/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.dispatcher; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.olat.basesecurity.AuthHelper; import org.olat.core.CoreSpringFactory; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.dispatcher.DispatcherModule; import org.olat.core.gui.UserRequest; import org.olat.core.gui.UserRequestImpl; import org.olat.core.gui.Windows; import org.olat.core.gui.components.Window; import org.olat.core.gui.components.form.flexible.impl.InvalidRequestParameterException; import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.control.ChiefControllerCreator; import org.olat.core.gui.exception.MsgFactory; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.session.UserSessionManager; import org.olat.login.oauth.OAuthLoginModule; import org.olat.login.oauth.OAuthResource; import org.olat.login.oauth.OAuthSPI; /** * Initial Date: 28.11.2003 * * @author Mike Stock */ public class DMZDispatcher implements Dispatcher { private static final OLog log = Tracing.createLoggerFor(DMZDispatcher.class); public static final String DMZDISPATCHER_BUSINESSPATH = "DMZDispatcher:businessPath"; private final boolean maintenance; /** * set by spring to create the starting workflow for /dmz/ */ private ChiefControllerCreator chiefControllerCreator; /** * set by spring */ private final Map<String, ChiefControllerCreator> dmzServicesByPath = new HashMap<>(); public DMZDispatcher(boolean maintenance) { this.maintenance = maintenance; } /** * OLAT-5165: check whether we are currently rejecting all dmz requests and if * the current request is not from an admin who did 'switch to node'. * <p> * @param request the incoming request * @param response the response object * @return whether or not to reject this request. upon true, the calling execute() method * will stop any further action and simply return */ private boolean rejectRequest(HttpServletRequest request, HttpServletResponse response) { if (AuthHelper.isRejectDMZRequests()) { boolean validBypass = false; Cookie[] cookies = request.getCookies(); Cookie sessionCookie = null; if (cookies!=null) { for(int i=0; i<cookies.length; i++) { Cookie cookie = cookies[i]; if ("bypassdmzreject".equals(cookie.getName())) { // there is a bypassdmzreject cookie set - let's check the time try{ long bypasscreationtime = Long.parseLong(cookie.getValue()); if (System.currentTimeMillis()-bypasscreationtime<5*60*1000) { log.info("Allowing request with valid bypass cookie, sessionId="+request.getRequestedSessionId()); validBypass = true; } } catch(NumberFormatException e) { // ignore } } else if ("JSESSIONID".equals(cookie.getName())) { sessionCookie = cookie; } } } if (!validBypass) { final String rejectUrl = request.getRequestURI(); log.info("Rejecting request to DMZDispatcher (AuthHelper.isRejectDMZRequests() is true) to "+rejectUrl+", sessionId="+request.getRequestedSessionId()); if (sessionCookie!=null) { String newSessionId = sessionCookie.getValue().substring(0, sessionCookie.getValue().length()-2); response.setHeader("Set-Cookie", "JSESSIONID="+newSessionId+"; Path="+request.getContextPath()+(request.isSecure()?"":"; Secure")); } DispatcherModule.redirectTo(response, rejectUrl); return true; } } return false; } /** * Main method called by OpenOLATServlet. This processess all requests for * users who are not authenticated. * * @param request * @param response * @param uriPrefix */ @Override public void execute(HttpServletRequest request, HttpServletResponse response) { if (rejectRequest(request, response)) { return; } UserRequest ureq = null; String uriPrefix = DispatcherModule.getLegacyUriPrefix(request); try { // upon creation URL is checked for ureq = new UserRequestImpl(uriPrefix, request, response); } catch (NumberFormatException nfe) { // MODE could not be decoded // typically if robots with wrong urls hit the system // or user have bookmarks // or authors copy-pasted links to the content. // showing redscreens for non valid URL is wrong instead // a 404 message must be shown -> e.g. robots correct their links. if (log.isDebug()) { log.debug("Bad Request " + request.getPathInfo()); } DispatcherModule.sendBadRequest(request.getPathInfo(), response); return; } //set load performance mode depending on logged in user or global parameter //here in the DMZ only the global parameter plays a role. //XX:GUIInterna.setLoadPerformanceMode(ureq); try { // find out about which subdispatcher is meant // e.g. got here because of /dmz/... // maybe something like /dmz/registration/ // // add the context path to align with uriPrefix e.g. /olat/dmz/ String pathInfo = request.getContextPath() + request.getPathInfo(); ChiefControllerCreator subPathccc = null; boolean dmzOnly = pathInfo.equals(uriPrefix);// if /olat/dmz/ if (!dmzOnly) { int sl = pathInfo.indexOf('/', uriPrefix.length()); String sub; if (sl > 1) { // e.g. something like /registration/ or /pwchange/ sub = pathInfo.substring(uriPrefix.length() - 1, sl + 1); } else { // e.g. something like /info.html from (/dmz/info.html) sub = pathInfo; } // chief controller creator for sub path, e.g. subPathccc = dmzServicesByPath.get(sub); if(subPathccc != null) { UserSession usess = ureq.getUserSession(); Windows ws = Windows.getWindows(usess); synchronized (ws) { //o_clusterOK by:fj per user session ChiefController occ = subPathccc.createChiefController(ureq); Window window = occ.getWindow(); window.setUriPrefix(uriPrefix); ws.registerWindow(window); window.dispatchRequest(ureq, true); return; } } }//else a /olat/dmz/ request UserSession usess = ureq.getUserSession(); Windows ws = Windows.getWindows(usess); //sync over the UserSession Instance as the Windows can be recreated in the synchronize block //and make it useless under heavily load or 2 concurrent requests synchronized (usess) { //o_clusterOK by:fj per user session Window window; boolean windowHere = ws.isExisting(uriPrefix, ureq.getWindowID()); boolean validDispatchUri = ureq.isValidDispatchURI(); if (validDispatchUri && !windowHere) { // probably valid framework link from previous user && new Session(no window): // when a previous user logged off, and 30min later (when the httpsession is invalidated), the next user clicks e.g. on // the log-in link in the -same- browser window -> // -> there is no window -> create a new one window = null; CoreSpringFactory.getImpl(UserSessionManager.class).signOffAndClear(usess); usess.setLocale(LocaleNegotiator.getPreferedLocale(ureq)); I18nManager.updateLocaleInfoToThread(usess);//update locale infos // request new windows since it is a new usersession, the old one was purged ws = Windows.getWindows(usess); } else if (validDispatchUri) { window = ws.getWindow(ureq); } else if (dmzOnly) { // e.g. /dmz/ -> start screen, clear previous session data window = null; CoreSpringFactory.getImpl(UserSessionManager.class).signOffAndClear(usess); usess.setLocale(LocaleNegotiator.getPreferedLocale(ureq)); I18nManager.updateLocaleInfoToThread(usess);//update locale infos OAuthLoginModule oauthModule = CoreSpringFactory.getImpl(OAuthLoginModule.class); if(canRedirectConfigurableOAuth(request, response, oauthModule)) { return; } else if(canRedirectOAuth(request, oauthModule)) { OAuthSPI oauthSpi = oauthModule.getRootProvider(); HttpSession session = request.getSession(); OAuthResource.redirect(oauthSpi, response, session); return; } // request new windows since it is a new usersession, the old one was purged ws = Windows.getWindows(usess); } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } if (window == null) { // no window found, -> start a new WorkFlow/Controller and obtain the window // main controller which also implements the windowcontroller for pagestatus and modal dialogs Object wSettings = usess.getEntry(WINDOW_SETTINGS); ChiefController occ = chiefControllerCreator.createChiefController(ureq); window = occ.getWindow(); window.setUriPrefix(uriPrefix); ws.registerWindow(window); String businessPath = (String) usess.removeEntryFromNonClearedStore(DMZDISPATCHER_BUSINESSPATH); if (businessPath != null) { List<ContextEntry> ces = BusinessControlFactory.getInstance().createCEListFromString(businessPath); window.getDTabs().activate(ureq, null, ces); } //apply the settings forward usess.putEntryInNonClearedStore(WINDOW_SETTINGS, wSettings); } window.dispatchRequest(ureq); } } catch (InvalidRequestParameterException e) { try { response.sendError(HttpServletResponse.SC_BAD_REQUEST); } catch (IOException e1) { log.error("An exception occured while handling the invalid request parameter exception...", e1); } } catch (Throwable th) { try { ChiefController msgcc = MsgFactory.createMessageChiefController(ureq, th); // the controller's window must be failsafe also msgcc.getWindow().dispatchRequest(ureq, true); // do not dispatch (render only), since this is a new Window created as // a result of another window's click. } catch (Throwable t) { log.error("An exception occured while handling the exception...", t); } } } private boolean canRedirectOAuth(HttpServletRequest request, OAuthLoginModule oauthModule) { boolean canRedirect; if(maintenance) { canRedirect = false; } else if(StringHelper.containsNonWhitespace(request.getParameter("logout"))) { canRedirect = false; } else if(oauthModule.getRootProvider() != null) { canRedirect = true; } else { canRedirect = false; } return canRedirect; } private boolean canRedirectConfigurableOAuth(HttpServletRequest request, HttpServletResponse response, OAuthLoginModule oauthModule) { String provider = request.getParameter("provider"); if(StringHelper.containsNonWhitespace(provider)) { OAuthSPI spi = oauthModule.getProvider(provider); if(spi != null) { HttpSession session = request.getSession(); OAuthResource.redirect(spi, response, session); return true; } } return false; } /** * called by spring only * * @param subdispatchers The subdispatchers to set. */ public void setDmzServicesByPath(Map<String, ChiefControllerCreator> servicesByPath) { if(servicesByPath != null) { dmzServicesByPath.putAll(servicesByPath); } } /** * @param chiefControllerCreator The chiefControllerCreator to set. */ public void setChiefControllerCreator(ChiefControllerCreator chiefControllerCreator) { this.chiefControllerCreator = chiefControllerCreator; } }