From 0ca11e0e2c83c7f59be91b866d39de57abf98758 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Mon, 3 Feb 2020 14:04:22 +0100
Subject: [PATCH] OO-4517: implements an exclusion list of WebDAV client per
 user agent

---
 .../commons/services/webdav/WebDAVModule.java |  19 ++-
 .../webdav/servlets/WebDAVDispatcherImpl.java |  19 ++-
 .../webdav/ui/WebDAVAdminController.java      | 116 ++++++++++--------
 .../ui/_i18n/LocalStrings_de.properties       |   2 +
 .../ui/_i18n/LocalStrings_en.properties       |   2 +
 .../resources/serviceconfig/olat.properties   |   2 +
 6 files changed, 104 insertions(+), 56 deletions(-)

diff --git a/src/main/java/org/olat/core/commons/services/webdav/WebDAVModule.java b/src/main/java/org/olat/core/commons/services/webdav/WebDAVModule.java
index a80d382a4df..8d206583153 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/WebDAVModule.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/WebDAVModule.java
@@ -70,7 +70,9 @@ public class WebDAVModule extends AbstractSpringModule implements ConfigOnOff {
 	private boolean prependCourseReferenceToTitle;
 	@Value("${webdav.basic.authentication.black.list}")
 	private String basicAuthenticationBlackList;
-
+	@Value("${webdav.user.agent.black.list}")
+	private String userAgentBlackList;
+	
 	@Value("${webdav.learners.bookmarks.enabled:true}")
 	private boolean enableLearnersBookmarksCourse;
 	/**
@@ -233,6 +235,21 @@ public class WebDAVModule extends AbstractSpringModule implements ConfigOnOff {
 		this.basicAuthenticationBlackList = basicAuthenticationBlackList;
 	}
 
+	public String[] getUserAgentBlackListArray() {
+		if(StringHelper.containsNonWhitespace(userAgentBlackList)) {
+			return userAgentBlackList.split("[,]");
+		}
+		return new String[0];
+	}
+
+	public String getUserAgentBlackList() {
+		return userAgentBlackList;
+	}
+
+	public void setUserAgentBlackList(String userAgentBlackList) {
+		this.userAgentBlackList = userAgentBlackList;
+	}
+
 	/**
 	 * Return an unmodifiable map
 	 * @return
diff --git a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
index 03a7820cee2..cad3154c7b2 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
@@ -54,9 +54,11 @@ import org.olat.core.commons.services.webdav.WebDAVDispatcher;
 import org.olat.core.commons.services.webdav.WebDAVManager;
 import org.olat.core.commons.services.webdav.WebDAVModule;
 import org.olat.core.dispatcher.Dispatcher;
+import org.olat.core.gui.media.ServletUtil;
 import org.olat.core.helpers.Settings;
 import org.apache.logging.log4j.Logger;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
 import org.olat.core.util.UserSession;
 import org.olat.core.util.vfs.QuotaExceededException;
 import org.olat.core.util.vfs.VFSItem;
@@ -273,7 +275,7 @@ public class WebDAVDispatcherImpl
 	throws ServletException, IOException {
 		if (webDAVManager == null) {
 			resp.setStatus(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
-		} else if(webDAVModule == null || !webDAVModule.isEnabled()) {
+		} else if(webDAVModule == null || !webDAVModule.isEnabled() || isUserAgentExcluded(req)) {
 			resp.setStatus(WebdavStatus.SC_FORBIDDEN);
 		} else if (webDAVManager.handleAuthentication(req, resp)) {
 			webdavService(req, resp);
@@ -281,6 +283,21 @@ public class WebDAVDispatcherImpl
 			//the method handleAuthentication will send the challenges for authentication
 		}
 	}
+	
+	private boolean isUserAgentExcluded(HttpServletRequest req) {
+		String userAgent = ServletUtil.getUserAgent(req);
+		if(!StringHelper.containsNonWhitespace(userAgent)) {
+			userAgent = "";
+		}
+		String[] blackList = webDAVModule.getUserAgentBlackListArray();
+		for(String blackListedAgent:blackList) {
+			if((blackListedAgent.length() < 2 && userAgent.equalsIgnoreCase(blackListedAgent))
+					|| (blackListedAgent.length() >= 2 && userAgent.contains(blackListedAgent))) {
+				return true;
+			}
+		}
+		return false;
+	}
 
 	/**
      * Handles the special WebDAV methods.
diff --git a/src/main/java/org/olat/core/commons/services/webdav/ui/WebDAVAdminController.java b/src/main/java/org/olat/core/commons/services/webdav/ui/WebDAVAdminController.java
index 457f814f107..37ad1d594ce 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/ui/WebDAVAdminController.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/ui/WebDAVAdminController.java
@@ -19,16 +19,20 @@
  */
 package org.olat.core.commons.services.webdav.ui;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.webdav.WebDAVModule;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.SpacerElement;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -39,6 +43,8 @@ public class WebDAVAdminController extends FormBasicController {
 	
 	private static final String[] onKeys = new String[]{"xx"};
 	
+	private TextElement excludeUserAgentsClientsEl;
+	private MultipleSelectionElement excludeClientsEl;
 	private MultipleSelectionElement enableModuleEl;
 	private MultipleSelectionElement enableLinkEl;
 	private MultipleSelectionElement enableDigestEl;
@@ -48,13 +54,17 @@ public class WebDAVAdminController extends FormBasicController {
 	private MultipleSelectionElement learnersAsParticipantEl;
 	private MultipleSelectionElement learnersBookmarkEl;
 	private MultipleSelectionElement prependReferenceEl;
+	private SpacerElement spacer1;
+	private SpacerElement spacer2;
 	
-	private final WebDAVModule webDAVModule;
+
+	@Autowired
+	private WebDAVModule webDAVModule;
 	
 	public WebDAVAdminController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
-		webDAVModule = CoreSpringFactory.getImpl(WebDAVModule.class);
 		initForm(ureq);
+		updateEnabledDisabled();
 	}
 
 	@Override
@@ -64,56 +74,51 @@ public class WebDAVAdminController extends FormBasicController {
 		setFormDescription("admin.webdav.description");
 		setFormContextHelp("WebDAV");
 
-
 		boolean enabled = webDAVModule.isEnabled();
 		String[] values = new String[] { getTranslator().translate("webdav.on") };
 		enableModuleEl = uifactory.addCheckboxesHorizontal("webdavModule", "webdav.module", formLayout, onKeys, values);
-		enableModuleEl.select("xx", enabled);
 		enableModuleEl.addActionListener(FormEvent.ONCHANGE);
+		enableModuleEl.select("xx", enabled);
 		
 		enableLinkEl = uifactory.addCheckboxesHorizontal("webdavLink", "webdav.link", formLayout, onKeys, values);
 		enableLinkEl.select("xx", webDAVModule.isLinkEnabled());
-		enableLinkEl.addActionListener(FormEvent.ONCHANGE);
-		enableLinkEl.setEnabled(enabled);
 		
 		enableDigestEl = uifactory.addCheckboxesHorizontal("webdavDigest", "webdav.digest", formLayout, onKeys, values);
 		enableDigestEl.select("xx", webDAVModule.isDigestAuthenticationEnabled());
-		enableDigestEl.addActionListener(FormEvent.ONCHANGE);
-		enableDigestEl.setEnabled(enabled);
+
+		String excludedUserAgents = webDAVModule.getUserAgentBlackList();
+		excludeClientsEl = uifactory.addCheckboxesHorizontal("webdavExclusion", "webdav.client.exclusion", formLayout, onKeys, values);
+		excludeClientsEl.select("xx", StringHelper.containsNonWhitespace(excludedUserAgents));
+		excludeClientsEl.addActionListener(FormEvent.ONCHANGE);
+		
+		excludeUserAgentsClientsEl = uifactory.addTextElement("webdav.user.agent.exclusion", 4096, excludedUserAgents, formLayout);
+		excludeUserAgentsClientsEl.setVisible(excludeClientsEl.isAtLeastSelected(1));
 		
-		uifactory.addSpacerElement("spacer1", formLayout, false);
+		spacer1 = uifactory.addSpacerElement("spacer1", formLayout, false);
 		
 		enableTermsFoldersEl = uifactory.addCheckboxesHorizontal("webdavTermsFolders", "webdav.termsfolders", formLayout, onKeys, values);
 		enableTermsFoldersEl.select("xx", webDAVModule.isTermsFoldersEnabled());
-		enableTermsFoldersEl.addActionListener(FormEvent.ONCHANGE);
-		enableTermsFoldersEl.setEnabled(enabled);
 		
 		enableCurriculumElementFoldersEl = uifactory.addCheckboxesHorizontal("webdavCurriculumsElementsFolders", "webdav.curriculumelementsfolders", formLayout, onKeys, values);
 		enableCurriculumElementFoldersEl.select("xx", webDAVModule.isCurriculumElementFoldersEnabled());
-		enableCurriculumElementFoldersEl.addActionListener(FormEvent.ONCHANGE);
-		enableCurriculumElementFoldersEl.setEnabled(enabled);
 		
 		enableManagedFoldersEl = uifactory.addCheckboxesHorizontal("webdavManagedFolders", "webdav.managedfolders", formLayout, onKeys, values);
 		enableManagedFoldersEl.select("xx", webDAVModule.isManagedFoldersEnabled());
-		enableManagedFoldersEl.addActionListener(FormEvent.ONCHANGE);
-		enableManagedFoldersEl.setEnabled(enabled);
 		
 		prependReferenceEl = uifactory.addCheckboxesHorizontal("webdavPrepend", "webdav.prepend.reference", formLayout, onKeys, values);
 		prependReferenceEl.select("xx", webDAVModule.isPrependCourseReferenceToTitle());
-		prependReferenceEl.addActionListener(FormEvent.ONCHANGE);
-		prependReferenceEl.setEnabled(enabled);
-
-		uifactory.addSpacerElement("spacer2", formLayout, false);
+		
+		spacer2 = uifactory.addSpacerElement("spacer2", formLayout, false);
 		
 		learnersAsParticipantEl = uifactory.addCheckboxesHorizontal("learnersParticipants", "webdav.for.learners.participants", formLayout, onKeys, values);
 		learnersAsParticipantEl.select("xx", webDAVModule.isEnableLearnersParticipatingCourses());
-		learnersAsParticipantEl.addActionListener(FormEvent.ONCHANGE);
-		learnersAsParticipantEl.setEnabled(enabled);
 		
 		learnersBookmarkEl = uifactory.addCheckboxesHorizontal("learnerBookmarks", "webdav.for.learners.bookmarks", formLayout, onKeys, values);
 		learnersBookmarkEl.select("xx", webDAVModule.isEnableLearnersBookmarksCourse());
-		learnersBookmarkEl.addActionListener(FormEvent.ONCHANGE);
-		learnersBookmarkEl.setEnabled(enabled);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormSubmitButton("save", buttonsCont);
 	}
 
 	@Override
@@ -126,41 +131,44 @@ public class WebDAVAdminController extends FormBasicController {
 		if(source == enableModuleEl) {
 			boolean enabled = enableModuleEl.isAtLeastSelected(1);
 			webDAVModule.setEnabled(enabled);
-			enableLinkEl.setEnabled(enabled);
-			enableDigestEl.setEnabled(enabled);
-			enableTermsFoldersEl.setEnabled(enabled);
-			learnersAsParticipantEl.setEnabled(enabled);
-			learnersBookmarkEl.setEnabled(enabled);
-		} else if(source == enableLinkEl) {
-			boolean enabled = enableLinkEl.isAtLeastSelected(1);
-			webDAVModule.setLinkEnabled(enabled);
-		} else if(source == enableDigestEl) {
-			boolean enabled = enableDigestEl.isAtLeastSelected(1);
-			webDAVModule.setDigestAuthenticationEnabled(enabled);
-		} else if(source == enableTermsFoldersEl) {
-			boolean enabled = enableTermsFoldersEl.isAtLeastSelected(1);
-			webDAVModule.setTermsFoldersEnabled(enabled);
-		} else if(source == enableCurriculumElementFoldersEl) {
-			boolean enabled = enableCurriculumElementFoldersEl.isAtLeastSelected(1);
-			webDAVModule.setCurriculumElementFoldersEnabled(enabled);
-		} else if(source == learnersAsParticipantEl) {
-			boolean enabled = learnersAsParticipantEl.isAtLeastSelected(1);
-			webDAVModule.setEnableLearnersParticipatingCourses(enabled);
-		} else if(source == learnersBookmarkEl) {
-			boolean enabled = learnersBookmarkEl.isAtLeastSelected(1);
-			webDAVModule.setEnableLearnersBookmarksCourse(enabled);
-		} else if(source == prependReferenceEl) {
-			boolean enabled = prependReferenceEl.isAtLeastSelected(1);
-			webDAVModule.setPrependCourseReferenceToTitle(enabled);
-		} else if(source == enableManagedFoldersEl) {
-			boolean enabled = enableManagedFoldersEl.isAtLeastSelected(1);
-			webDAVModule.setManagedFoldersEnabled(enabled);
+			updateEnabledDisabled();
+		} else if(source == excludeClientsEl) {
+			excludeUserAgentsClientsEl.setVisible(excludeClientsEl.isAtLeastSelected(1));
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
+	
+	private void updateEnabledDisabled() {
+		boolean enabled = enableModuleEl.isAtLeastSelected(1);
+		
+		enableLinkEl.setVisible(enabled);
+		enableDigestEl.setVisible(enabled);
+		enableTermsFoldersEl.setVisible(enabled);
+		learnersAsParticipantEl.setVisible(enabled);
+		learnersBookmarkEl.setVisible(enabled);
+		enableCurriculumElementFoldersEl.setVisible(enabled);
+		enableManagedFoldersEl.setVisible(enabled);
+		prependReferenceEl.setVisible(enabled);
+		excludeClientsEl.setVisible(enabled);
+		excludeUserAgentsClientsEl.setVisible(enabled && excludeClientsEl.isAtLeastSelected(1));
+		spacer1.setVisible(enabled);
+		spacer2.setVisible(enabled);
+	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
-		//
+		webDAVModule.setLinkEnabled(enableLinkEl.isAtLeastSelected(1));
+		webDAVModule.setDigestAuthenticationEnabled(enableDigestEl.isAtLeastSelected(1));
+		webDAVModule.setTermsFoldersEnabled(enableTermsFoldersEl.isAtLeastSelected(1));
+		webDAVModule.setCurriculumElementFoldersEnabled(enableCurriculumElementFoldersEl.isAtLeastSelected(1));
+		webDAVModule.setEnableLearnersParticipatingCourses(learnersAsParticipantEl.isAtLeastSelected(1));
+		webDAVModule.setEnableLearnersBookmarksCourse(learnersBookmarkEl.isAtLeastSelected(1));
+		webDAVModule.setPrependCourseReferenceToTitle(prependReferenceEl.isAtLeastSelected(1));
+		webDAVModule.setManagedFoldersEnabled(enableManagedFoldersEl.isAtLeastSelected(1));
+		if(excludeClientsEl.isAtLeastSelected(1)) {
+			webDAVModule.setUserAgentBlackList(excludeUserAgentsClientsEl.getValue());
+		} else {
+			webDAVModule.setUserAgentBlackList(null);
+		}
 	}
 }
diff --git a/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_de.properties
index e081425ac73..97cd86527ad 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_de.properties
@@ -3,6 +3,7 @@ admin.menu.title=WebDAV
 admin.menu.title.alt=WebDAV Zugang
 admin.webdav.description=Mit Hilfe von WebDAV k\u00F6nnen Sie OpenOlat Ordner auf Ihrem lokalen Desktop wie lokale Ordner anzeigen und verwenden. Konfigurieren Sie ob diese Funktion allen Benutzern Systemweit zur Verf\u00FCgung stehen soll. Bitte lesen sie die Kontexthilfe.
 core.webdav=WebDAV
+webdav.client.exclusion=WebDAV Client Verbot
 webdav.curriculumelementsfolders=Kurse nach Curriculumelementen gruppieren
 webdav.digest=Digest Authentication bei HTTP Zugang verwenden 
 webdav.for.learners.bookmarks=Zugriff f\u00FCr Studenten / Betreuer Favoriten
@@ -13,3 +14,4 @@ webdav.module=WebDAV Zugang
 webdav.on=ein
 webdav.prepend.reference=Kennzeichen dem Titel voranstellen
 webdav.termsfolders=Kurse nach Semesterdaten gruppieren
+webdav.user.agent.exclusion=Liste von User-Agent (Komma als Trenzeichen)
diff --git a/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_en.properties
index a4a726fe021..28fe7f470a7 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/core/commons/services/webdav/ui/_i18n/LocalStrings_en.properties
@@ -3,6 +3,7 @@ admin.menu.title=WebDAV
 admin.menu.title.alt=WebDAV access
 admin.webdav.description=Using WebDAV you can mount and use OpenOlat folders on your local desktop as if they were local folders. Enable this feature to make it accessable to all users of your platform. Please read the context help.
 core.webdav=WebDAV
+webdav.client.exclusion=WebDAV Client exclusion
 webdav.curriculumelementsfolders=Group courses by curriculum elements
 webdav.digest=Digest Authentication for HTTP access
 webdav.for.learners.bookmarks=Enable access for courses that users marked as favorite
@@ -13,3 +14,4 @@ webdav.module=WebDAV access
 webdav.on=enabled
 webdav.prepend.reference=Prepend external course reference to title
 webdav.termsfolders=Group courses by semester terms
+webdav.user.agent.exclusion=List of User-Agents (comma as separator)
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index 7b34f89ff55..7a27ea40234 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -563,6 +563,8 @@ auth.digest.enabled=true
 webdav.termsfolders.enabled=true
 # User agents for which the basic authentication should never be proposed
 webdav.basic.authentication.black.list=Microsoft Office Excel,Microsoft Excel,Microsoft-WebDAV-MiniRedir
+# User agents which don't play nice
+webdav.user.agent.black.list=-
 
 ########################################################################
 # Image and PDF scale/thumbnail options
-- 
GitLab