From 7d1f1078bcd57b8a2af8052e3221c8614f8e1bb0 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 24 Jul 2013 14:38:37 +0200
Subject: [PATCH] OO-672: authorize access to the system based on a list of IPs

---
 .../java/org/olat/restapi/RestModule.java     | 24 ++++++
 .../olat/restapi/_spring/restApiContext.xml   |  1 +
 .../restapi/security/RestApiLoginFilter.java  | 73 +++++++++++++++++++
 .../restapi/security/RestSecurityHelper.java  | 20 +++++
 .../olat/restapi/system/LogWebService.java    | 12 +--
 .../restapi/system/RuntimeWebService.java     | 15 ----
 .../olat/restapi/system/SystemWebService.java | 14 ++--
 .../resources/serviceconfig/olat.properties   |  5 ++
 8 files changed, 132 insertions(+), 32 deletions(-)

diff --git a/src/main/java/org/olat/restapi/RestModule.java b/src/main/java/org/olat/restapi/RestModule.java
index f35676896bb..aae2c36d839 100644
--- a/src/main/java/org/olat/restapi/RestModule.java
+++ b/src/main/java/org/olat/restapi/RestModule.java
@@ -19,6 +19,10 @@
  */
 package org.olat.restapi;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
 import org.olat.core.configuration.PersistedProperties;
 import org.olat.core.configuration.PersistedPropertiesChangedEvent;
 import org.olat.core.gui.control.Event;
@@ -41,6 +45,7 @@ public class RestModule implements GenericEventListener {
 	
 	private Boolean enabled;
 	private Boolean defaultEnabled;
+	private String ipsByPass;
 	private PersistedProperties persistedProperties;
 	
 	private String monitoredProbes;
@@ -84,6 +89,25 @@ public class RestModule implements GenericEventListener {
 		}
 	}
 	
+	public String getIpsByPass() {
+		return ipsByPass;
+	}
+	
+	public List<String> getIpsWithSystemAccess() {
+		List<String> ips = new ArrayList<String>();
+		for(StringTokenizer tokenizer=new StringTokenizer(ipsByPass, ",;|"); tokenizer.hasMoreTokens(); ) {
+			String token = tokenizer.nextToken();
+			if(StringHelper.containsNonWhitespace(token)) {
+				ips.add(token);
+			}
+		}
+		return ips;
+	}
+
+	public void setIpsByPass(String ipsByPass) {
+		this.ipsByPass = ipsByPass;
+	}
+
 	/**
 	 * @return the persisted properties
 	 */
diff --git a/src/main/java/org/olat/restapi/_spring/restApiContext.xml b/src/main/java/org/olat/restapi/_spring/restApiContext.xml
index 28509b66341..471bd36de1b 100644
--- a/src/main/java/org/olat/restapi/_spring/restApiContext.xml
+++ b/src/main/java/org/olat/restapi/_spring/restApiContext.xml
@@ -14,6 +14,7 @@
 				<constructor-arg index="1" ref="restModule" />
 			</bean>
 		</property>
+		<property name="ipsByPass" value="${restapi.ips.system}"/>
 	</bean>
 
 	<bean id="org.olat.restapi.security.RestSecurityBean" class="org.olat.restapi.security.RestSecurityBeanImpl">
diff --git a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
index 0f32e562bfb..af3d3e53de0 100644
--- a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
+++ b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
@@ -20,6 +20,8 @@
 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;
@@ -43,9 +45,11 @@ 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;
@@ -73,6 +77,7 @@ public class RestApiLoginFilter implements Filter {
 	
 	private static List<String> openUrls;
 	private static List<String> alwaysEnabledUrls;
+	private static List<String> ipProtectedUrls;
 	private static String LOGIN_URL;
 	
 	/**
@@ -121,6 +126,9 @@ public class RestApiLoginFilter implements Filter {
 						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);
 						
@@ -211,6 +219,18 @@ public class RestApiLoginFilter implements Filter {
 		return false;
 	}
 	
+	private boolean isRequestURIInIPProtectedSpace(String requestURI, HttpServletRequest httpRequest, RestModule restModule) {
+		for(String openURI : getIPProtectedURIs()) {
+			if(requestURI.startsWith(openURI)) {
+				String remoteAddr = httpRequest.getRemoteAddr();
+				if(StringHelper.containsNonWhitespace(remoteAddr)) {
+					return restModule.getIpsWithSystemAccess().contains(remoteAddr);
+				}
+			}
+		}
+		return false;
+	}
+	
 	private boolean isRequestURIAlwaysEnabled(String requestURI) {
 		for(String openURI : getAlwaysEnabledURIs()) {
 			if(requestURI.startsWith(openURI)) {
@@ -271,6 +291,50 @@ public class RestApiLoginFilter implements Filter {
 		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);
@@ -358,4 +422,13 @@ public class RestApiLoginFilter implements Filter {
 		}
 		return openUrls;
 	}
+	
+	private List<String> getIPProtectedURIs() {
+		if(ipProtectedUrls == null) {
+			String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT);
+			ipProtectedUrls = new ArrayList<String>();
+			ipProtectedUrls.add(context + "/system");
+		}
+		return ipProtectedUrls;
+	}
 }
diff --git a/src/main/java/org/olat/restapi/security/RestSecurityHelper.java b/src/main/java/org/olat/restapi/security/RestSecurityHelper.java
index ad806a1ac05..704919ee879 100644
--- a/src/main/java/org/olat/restapi/security/RestSecurityHelper.java
+++ b/src/main/java/org/olat/restapi/security/RestSecurityHelper.java
@@ -20,6 +20,7 @@
 package org.olat.restapi.security;
 
 import java.util.Locale;
+import java.util.UUID;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -51,6 +52,8 @@ public class RestSecurityHelper {
 	public static final String SEC_TOKEN = "X-OLAT-TOKEN";
 	public static final String SEC_USER_REQUEST = "olat-user-request";
 	
+	protected static final String SYSTEM_MARKER = UUID.randomUUID().toString();
+	
 	
 	public static UserRequest getUserRequest(HttpServletRequest request) {
 		return (UserRequest)request.getAttribute(SEC_USER_REQUEST);
@@ -147,6 +150,23 @@ public class RestSecurityHelper {
 		}
 	}
 	
+	public static boolean isAdminOrSystem(HttpServletRequest request) {
+		try {
+			Roles roles = getRoles(request);
+			if(roles.isOLATAdmin()) {
+				return true;
+			}
+			UserRequest ureq = (UserRequest)request.getAttribute(SEC_USER_REQUEST);
+			if(ureq != null && ureq.getUserSession() != null
+					&& ureq.getUserSession().getEntry(SYSTEM_MARKER) != null) {
+				return true;
+			}
+			return false;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+	
 	public static Roles getRoles(HttpServletRequest request) {
 		UserRequest ureq= (UserRequest)request.getAttribute(SEC_USER_REQUEST);
 		if(ureq == null || ureq.getUserSession() == null || ureq.getUserSession().getRoles() == null) {
diff --git a/src/main/java/org/olat/restapi/system/LogWebService.java b/src/main/java/org/olat/restapi/system/LogWebService.java
index 74018b4dc52..db14782ce3a 100644
--- a/src/main/java/org/olat/restapi/system/LogWebService.java
+++ b/src/main/java/org/olat/restapi/system/LogWebService.java
@@ -19,8 +19,6 @@
  */
 package org.olat.restapi.system;
 
-import static org.olat.restapi.security.RestSecurityHelper.isAdmin;
-
 import java.io.InputStream;
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -79,12 +77,6 @@ public class LogWebService {
 	@Path("{date}")
 	@Produces({ "text/plain", MediaType.APPLICATION_OCTET_STREAM })
 	public Response getLogFileByDate(@PathParam("date") String dateString, @Context HttpServletRequest request) {
-
-		// logfile download only allowed for admins!
-		if (!isAdmin(request)) {
-			return Response.serverError().status(Status.UNAUTHORIZED).build();
-		}
-
 		VFSLeaf logFile;
 		try {
 			logFile = logFileFromParam(dateString);
@@ -95,11 +87,11 @@ public class LogWebService {
 			return Response.serverError().status(Status.NOT_FOUND).build();
 
 		InputStream is = logFile.getInputStream();
-		if (is == null)
+		if (is == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
+		}
 
 		return Response.ok(is).cacheControl(cc).build(); // success
-
 	}
 	
 	@GET
diff --git a/src/main/java/org/olat/restapi/system/RuntimeWebService.java b/src/main/java/org/olat/restapi/system/RuntimeWebService.java
index bb8e1ce0f1d..f4082d1df1f 100644
--- a/src/main/java/org/olat/restapi/system/RuntimeWebService.java
+++ b/src/main/java/org/olat/restapi/system/RuntimeWebService.java
@@ -19,8 +19,6 @@
  */
 package org.olat.restapi.system;
 
-import static org.olat.restapi.security.RestSecurityHelper.isAdmin;
-
 import java.lang.management.ClassLoadingMXBean;
 import java.lang.management.GarbageCollectorMXBean;
 import java.lang.management.ManagementFactory;
@@ -71,10 +69,6 @@ public class RuntimeWebService {
 	@GET
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getSystemSummaryVO(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
-			return null;
-		}
-		
 		RuntimeStatisticsVO stats = new RuntimeStatisticsVO();
 		stats.setMemory(getMemoryStatistics());
 		stats.setThreads(getThreadStatistics());
@@ -104,9 +98,6 @@ public class RuntimeWebService {
 	@Path("memory")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getMemoryStatistics(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
-			return null;
-		}
 		MemoryStatisticsVO stats = getMemoryStatistics();
 		return Response.ok(stats).build();
 	}
@@ -126,9 +117,6 @@ public class RuntimeWebService {
 	@Path("threads")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getThreadStatistics(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
-			return null;
-		}
 		ThreadStatisticsVO stats = getThreadStatistics();
 		return Response.ok(stats).build();
 	}
@@ -147,9 +135,6 @@ public class RuntimeWebService {
 	@Path("classes")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getCompilationXml(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
-			return null;
-		}
 		ClasseStatisticsVO stats = getClasseStatistics();
 		return Response.ok(stats).build();
 	}
diff --git a/src/main/java/org/olat/restapi/system/SystemWebService.java b/src/main/java/org/olat/restapi/system/SystemWebService.java
index f3b27963c14..fcd516831d3 100644
--- a/src/main/java/org/olat/restapi/system/SystemWebService.java
+++ b/src/main/java/org/olat/restapi/system/SystemWebService.java
@@ -19,7 +19,7 @@
  */
 package org.olat.restapi.system;
 
-import static org.olat.restapi.security.RestSecurityHelper.isAdmin;
+import static org.olat.restapi.security.RestSecurityHelper.isAdminOrSystem;
 
 import java.lang.management.ManagementFactory;
 import java.lang.management.OperatingSystemMXBean;
@@ -55,7 +55,7 @@ public class SystemWebService {
 	
 	@Path("log")
 	public LogWebService getLogsWS(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
+		if(!isAdminOrSystem(request)) {
 			return null;
 		}
 		return new LogWebService();
@@ -75,7 +75,7 @@ public class SystemWebService {
 	@Path("environment")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getEnvironnementXml(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
+		if(!isAdminOrSystem(request)) {
 			return null;
 		}
 		
@@ -99,7 +99,7 @@ public class SystemWebService {
 	@Path("release")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getReleaseInfos(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
+		if(!isAdminOrSystem(request)) {
 			return null;
 		}
 		
@@ -112,7 +112,7 @@ public class SystemWebService {
 	
 	@Path("monitoring")
 	public MonitoringWebService getImplementedProbes(@Context HttpServletRequest request) {
-		if(!isMonitoringEnabled() && !isAdmin(request)) {
+		if(!isMonitoringEnabled() && !isAdminOrSystem(request)) {
 			return null;
 		}
 		return new MonitoringWebService();
@@ -120,7 +120,7 @@ public class SystemWebService {
 	
 	@Path("indexer")
 	public IndexerWebService getIndexer(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
+		if(!isAdminOrSystem(request)) {
 			return null;
 		}
 		return new IndexerWebService();
@@ -128,7 +128,7 @@ public class SystemWebService {
 	
 	@Path("notifications")
 	public NotificationsAdminWebService getNotifications(@Context HttpServletRequest request) {
-		if(!isAdmin(request)) {
+		if(!isAdminOrSystem(request)) {
 			return null;
 		}
 		return new NotificationsAdminWebService();
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index 45a5572adfd..089311e350f 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -624,6 +624,11 @@ search.indexing.cronjob.expression=0 0 3 * * ?
 restapi.enable=false
 restapi.enable.values=true,values
 
+#Access to the /restapi/system without authentication if the IP
+# of the client is in the list of IPs separated by comma 
+restapi.ips.system=
+restapi.ips.system.values=192.168.1.200,192.168.1.201
+
 ########################################################################
 # Security
 ########################################################################
-- 
GitLab