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