/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <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 the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <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> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.restapi.security; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.AuthHelper; import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.UserRequestImpl; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.ThreadLocalUserActivityLoggerInstaller; import org.olat.core.util.SessionInfo; import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.WebappHelper; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.session.UserSessionManager; import org.olat.login.auth.OLATAuthManager; import org.olat.restapi.RestModule; /** * * Description:<br> * Filter which protects the REST Api. * * <P> * Initial Date: 7 apr. 2010 <br> * @author srosse, stephane.rosse@frentix.com */ public class RestApiLoginFilter implements Filter { private static OLog log = Tracing.createLoggerFor(RestApiLoginFilter.class); private static final String BASIC_AUTH_REALM = "OLAT Rest API"; private static List<String> openUrls; private static List<String> alwaysEnabledUrls; private static List<String> ipProtectedUrls; private static String LOGIN_URL; /** * The survive time of the session used by token based authentication. For every request * is a new session created. */ private static int TOKEN_BASED_SESSION_TIMEOUT = 120; @Override public void init(FilterConfig filterConfig) { // } @Override public void destroy() { // } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException { if(request instanceof HttpServletRequest) { try { HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponse httpResponse = (HttpServletResponse)response; String requestURI = httpRequest.getRequestURI(); RestModule restModule = (RestModule)CoreSpringFactory.getBean("restModule"); if(restModule == null || !restModule.isEnabled() && !isRequestURIAlwaysEnabled(requestURI)) { httpResponse.sendError(403); return; } // initialize tracing with request, this allows debugging information as IP, User-Agent. Tracing.setUreq(httpRequest); I18nManager.attachI18nInfoToThread(httpRequest); ThreadLocalUserActivityLoggerInstaller.initUserActivityLogger(httpRequest); UserSession uress = CoreSpringFactory.getImpl(UserSessionManager.class).getUserSessionIfAlreadySet(httpRequest); if(uress != null && uress.isAuthenticated()) { //use the available session followSession(httpRequest, httpResponse, chain); } else { if(isRequestURIInLoginSpace(requestURI)) { followForAuthentication(requestURI, uress, httpRequest, httpResponse, chain); } else if(isRequestURIInOpenSpace(requestURI)) { followWithoutAuthentication(httpRequest, httpResponse, chain); } else if( isRequestURIInIPProtectedSpace(requestURI, httpRequest, restModule)) { upgradeIpAuthentication(httpRequest, httpResponse); followWithoutAuthentication(httpRequest, httpResponse, chain); } else if (isRequestTokenValid(httpRequest)) { String token = httpRequest.getHeader(RestSecurityHelper.SEC_TOKEN); followToken(token, httpRequest, httpResponse, chain); } else if (isBasicAuthenticated(httpRequest, httpResponse, requestURI)) { followBasicAuthenticated(request, response, chain); } else { httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"" + BASIC_AUTH_REALM + "\""); httpResponse.sendError(401); } } } catch (Exception e) { log.error("", e); } finally { ThreadLocalUserActivityLoggerInstaller.resetUserActivityLogger(); I18nManager.remove18nInfoFromThread(); Tracing.setUreq(null); DBFactory.getInstance().commitAndCloseSession(); } } else { throw new ServletException("Only accept HTTP Request"); } } private boolean isBasicAuthenticated(HttpServletRequest request, HttpServletResponse response, String requestURI) { String authHeader = request.getHeader("Authorization"); if (authHeader != null) { StringTokenizer st = new StringTokenizer(authHeader); if (st.hasMoreTokens()) { String basic = st.nextToken(); // We only handle HTTP Basic authentication if (basic.equalsIgnoreCase("Basic")) { String credentials = st.nextToken(); String userPass = StringHelper.decodeBase64(credentials); // The decoded string is in the form "userID:password". int p = userPass.indexOf(":"); if (p != -1) { String username = userPass.substring(0, p); String password = userPass.substring(p + 1); OLATAuthManager olatAuthenticationSpi = CoreSpringFactory.getImpl(OLATAuthManager.class); Identity identity = olatAuthenticationSpi.authenticate(null, username, password); if(identity == null) { return false; } UserRequest ureq = null; try{ //upon creation URL is checked for ureq = new UserRequestImpl(requestURI, request, response); } catch(NumberFormatException nfe) { return false; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); int loginStatus = AuthHelper.doHeadlessLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq, true); if (loginStatus == AuthHelper.LOGIN_OK) { UserDeletionManager.getInstance().setIdentityAsActiv(identity); //Forge a new security token RestSecurityBean securityBean = CoreSpringFactory.getImpl(RestSecurityBean.class); String token = securityBean.generateToken(identity, request.getSession()); response.setHeader(RestSecurityHelper.SEC_TOKEN, token); } return true; } } } } return false; } private void followBasicAuthenticated(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { chain.doFilter(request, response); } private boolean isRequestTokenValid(HttpServletRequest request) { String token = request.getHeader(RestSecurityHelper.SEC_TOKEN); RestSecurityBean securityBean = CoreSpringFactory.getImpl(RestSecurityBean.class); return securityBean.isTokenRegistrated(token, request.getSession(true)); } private boolean isRequestURIInLoginSpace(String requestURI) { String loginUrl = getLoginUrl(); if(loginUrl != null && requestURI.startsWith(loginUrl)) { return true; } return false; } private boolean isRequestURIInOpenSpace(String requestURI) { List<String> uris = getOpenURIs(); if(uris == null) return false; for(String openURI : uris) { if(requestURI.startsWith(openURI)) { return true; } } return false; } private boolean isRequestURIInIPProtectedSpace(String requestURI, HttpServletRequest httpRequest, RestModule restModule) { List<String> uris = getIPProtectedURIs(); if(uris == null) return false; for(String openURI : uris) { if(requestURI.startsWith(openURI)) { String remoteAddr = httpRequest.getRemoteAddr(); if(StringHelper.containsNonWhitespace(remoteAddr)) { return restModule.getIpsWithSystemAccess().contains(remoteAddr); } } } return false; } private boolean isRequestURIAlwaysEnabled(String requestURI) { List<String> uris = getAlwaysEnabledURIs(); if(uris == null) return false; for(String openURI : uris) { if(requestURI.startsWith(openURI)) { return true; } } return false; } private void followForAuthentication(String requestURI, UserSession uress, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { //create a session for login without security check if(uress == null) { uress = CoreSpringFactory.getImpl(UserSessionManager.class).getUserSession(request); } UserRequest ureq = null; try{ //upon creation URL is checked for ureq = new UserRequestImpl(requestURI, request, response); } catch(NumberFormatException nfe) { response.sendError(401); return; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); chain.doFilter(request, response); } private void followWithoutAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { UserSession uress = CoreSpringFactory.getImpl(UserSessionManager.class).getUserSessionIfAlreadySet(request); if(uress != null && uress.isAuthenticated()) { //is authenticated by session cookie, follow its current session followSession(request, response, chain); return; } String token = request.getHeader(RestSecurityHelper.SEC_TOKEN); RestSecurityBean securityBean = (RestSecurityBean)CoreSpringFactory.getBean(RestSecurityBean.class); if(StringHelper.containsNonWhitespace(token) && securityBean.isTokenRegistrated(token, request.getSession(true))) { //is authenticated by token, follow its current token followToken(token, request, response, chain); return; } //fxdiff FXOLAT-113: business path in DMZ UserRequest ureq = null; try{ //upon creation URL is checked for String requestURI = request.getRequestURI(); ureq = new UserRequestImpl(requestURI, request, response); } catch(NumberFormatException nfe) { response.sendError(401); return; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); //no authentication, but no authentication needed, go further chain.doFilter(request, response); } private void upgradeIpAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { UserSessionManager sessionManager = CoreSpringFactory.getImpl(UserSessionManager.class); UserSession usess = sessionManager.getUserSessionIfAlreadySet(request); if(usess == null) { usess = sessionManager.getUserSession(request.getSession(true)); } if(usess.getIdentity() == null) { usess.setRoles(new Roles(false, false, false, false, false, false, false)); String remoteAddr = request.getRemoteAddr(); SessionInfo sinfo = new SessionInfo(new Long(-1), "REST", request.getSession()); sinfo.setFirstname("REST"); sinfo.setLastname(remoteAddr); sinfo.setFromIP(remoteAddr); sinfo.setFromFQN(remoteAddr); try { InetAddress[] iaddr = InetAddress.getAllByName(request.getRemoteAddr()); if (iaddr.length > 0) sinfo.setFromFQN(iaddr[0].getHostName()); } catch (UnknownHostException e) { // ok, already set IP as FQDN } sinfo.setAuthProvider("IP"); sinfo.setUserAgent(request.getHeader("User-Agent")); sinfo.setSecure(request.isSecure()); sinfo.setREST(true); sinfo.setWebModeFromUreq(null); // set session info for this session usess.setSessionInfo(sinfo); } UserRequest ureq = null; try{ //upon creation URL is checked for String requestURI = request.getRequestURI(); ureq = new UserRequestImpl(requestURI, request, response); ureq.getUserSession().putEntryInNonClearedStore(RestSecurityHelper.SYSTEM_MARKER, Boolean.TRUE); } catch(NumberFormatException nfe) { response.sendError(401); return; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); } private void followToken(String token, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { HttpSession session = request.getSession(true); session.setMaxInactiveInterval(TOKEN_BASED_SESSION_TIMEOUT); UserSession uress = CoreSpringFactory.getImpl(UserSessionManager.class).getUserSession(session); if(uress != null) { UserRequest ureq = null; try{ //upon creation URL is checked for String requestURI = request.getRequestURI(); ureq = new UserRequestImpl(requestURI, request, response); } catch(Exception e) { response.sendError(500); return; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); RestSecurityBean securityBean = (RestSecurityBean)CoreSpringFactory.getBean(RestSecurityBean.class); Identity identity = securityBean.getIdentity(token); int loginStatus = AuthHelper.doHeadlessLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq, true); if(loginStatus == AuthHelper.LOGIN_OK) { response.setHeader(RestSecurityHelper.SEC_TOKEN, securityBean.renewToken(token)); synchronized(uress) { chain.doFilter(request, response); } } else response.sendError(401); } else response.sendError(401); } private void followSession(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { UserSession uress = CoreSpringFactory.getImpl(UserSessionManager.class).getUserSessionIfAlreadySet(request); if(uress != null && uress.isAuthenticated()) { UserRequest ureq = null; try{ //upon creation URL is checked for String requestURI = request.getRequestURI(); ureq = new UserRequestImpl(requestURI, request, response); } catch(NumberFormatException nfe) { response.sendError(401); return; } request.setAttribute(RestSecurityHelper.SEC_USER_REQUEST, ureq); synchronized(uress) { chain.doFilter(request, response); } } else { response.sendError(401); } } private boolean isWebappHelperInitiated() { if(Settings.isJUnitTest()) { return true; } return WebappHelper.getServletContextPath() != null; } private String getLoginUrl() { if(LOGIN_URL == null && isWebappHelperInitiated()) { String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT); LOGIN_URL = context + "/auth"; } return LOGIN_URL; } private List<String> getAlwaysEnabledURIs() { if(alwaysEnabledUrls == null && isWebappHelperInitiated() ) { String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT); List<String > urls = new ArrayList<String>(); urls.add(context + "/i18n"); urls.add(context + "/api"); urls.add(context + "/ping"); urls.add(context + "/openmeetings"); urls.add(context + "/system"); alwaysEnabledUrls = urls; } return alwaysEnabledUrls; } private List<String> getOpenURIs() { if(openUrls == null && isWebappHelperInitiated()) { String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT); List<String > urls = new ArrayList<String>(); urls.add(context + "/i18n"); urls.add(context + "/api"); urls.add(context + "/ping"); urls.add(context + "/application.wadl"); urls.add(context + "/application.html"); urls.add(context + "/wadl"); urls.add(context + "/registration"); urls.add(context + "/openmeetings"); openUrls = urls; } return openUrls; } private List<String> getIPProtectedURIs() { if(ipProtectedUrls == null && isWebappHelperInitiated()) { String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT); List<String > urls = new ArrayList<String>(); urls.add(context + "/system"); ipProtectedUrls = urls; } return ipProtectedUrls; } }