Skip to content

Commit

Permalink
feat(Authoring): Run virus scanner on asset upload (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffreykwan authored Nov 23, 2022
1 parent 13b2e63 commit 208558d
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 25 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,11 @@
<artifactId>tika-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>xyz.capybara</groupId>
<artifactId>clamav-client</artifactId>
<version>2.1.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
18 changes: 17 additions & 1 deletion scripts/beforeInstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,20 @@ systemctl start sysstat
echo "Set log file max size to 1G"
sed 's/{/{\n maxsize 1G/g' -i /etc/logrotate.d/rsyslog
sed 's/{/{\n maxsize 1G/g' -i /etc/logrotate.d/nginx
sed 's/{/{\n maxsize 1G/g' -i /etc/logrotate.d/tomcat9
sed 's/{/{\n maxsize 1G/g' -i /etc/logrotate.d/tomcat9

echo "Installing Clam AV"
apt-get install clamav clamav-daemon -y
systemctl stop clamav-freshclam

echo "Updating Clam AV database"
freshclam

echo "Updating Clam AV configuration"
echo -e "TCPSocket 3310\nTCPAddr 127.0.0.1" | tee -a /etc/clamav/clamd.conf

echo "Starting Clam AV"
systemctl enable clamav-freshclam
systemctl start clamav-freshclam
systemctl enable clamav-daemon
systemctl start clamav-daemon
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MimeTypes;
import org.apache.tika.parser.AutoDetectParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
Expand All @@ -38,6 +37,10 @@
import org.wise.portal.service.user.UserService;
import org.wise.vle.utils.FileManager;

import xyz.capybara.clamav.ClamavClient;
import xyz.capybara.clamav.ClamavException;
import xyz.capybara.clamav.commands.scan.result.ScanResult;

/**
* Project Asset API endpoint
*
Expand All @@ -58,6 +61,10 @@ public class ProjectAssetAPIController {
@Autowired
protected Environment appProperties;

private String EXCEEDED_MAX_PROJECT_SIZE_MESSAGE = "Exceeded project max asset size.\n"
+ "Please delete unused assets.\n\nContact WISE if your project needs more disk space.";
private String UPLOADING_THIS_FILE_NOT_ALLOWED_MESSAGE = "Uploading this file is not allowed.";

@GetMapping("/{projectId}")
@ResponseBody
protected Map<String, Object> getProjectAssets(Authentication auth, @PathVariable Long projectId)
Expand All @@ -82,51 +89,89 @@ protected Map<String, Object> saveProjectAsset(Authentication auth, @PathVariabl
result.put("error", new ArrayList<Object>());
String projectAssetsDirPath = getProjectAssetsDirectoryPath(project);
File projectAssetsDir = new File(projectAssetsDirPath);
ClamavClient clamavClient = getClamavClient();
for (MultipartFile file : files) {
addAsset(project, projectAssetsDir, file, user, result);
addAsset(project, projectAssetsDir, file, user, result, clamavClient);
}
result.put("assetDirectoryInfo", projectService.getDirectoryInfo(projectAssetsDir));
return result;
}
return null;
}

private ClamavClient getClamavClient() {
ClamavClient clamavClient = null;
String clamavServerAddress = appProperties.getProperty("clamav.server.address");
if (clamavServerAddress != null) {
try {
clamavClient = new ClamavClient(clamavServerAddress);
} catch (ClamavException e) {
e.printStackTrace();
}
}
return clamavClient;
}

@SuppressWarnings("unchecked")
private void addAsset(Project project, File projectAssetsDir, MultipartFile file, User user,
Map<String, Object> result) throws IOException {
long sizeOfAssetsDirectory = FileUtils.sizeOfDirectory(projectAssetsDir);
Long projectMaxTotalAssetsSize = project.getMaxTotalAssetsSize();
if (projectMaxTotalAssetsSize == null) {
projectMaxTotalAssetsSize = new Long(
appProperties.getProperty("project_max_total_assets_size", "15728640"));
}
Map<String, Object> result, ClamavClient clamavClient) throws IOException {
HashMap<String, String> fileObject = new HashMap<String, String>();
fileObject.put("filename", file.getOriginalFilename());
if (!isUserAllowedToUpload(user, file)) {
fileObject.put("message", "Upload file not allowed.");
((ArrayList<HashMap<String, String>>) result.get("error")).add(fileObject);
} else if (sizeOfAssetsDirectory + file.getSize() > projectMaxTotalAssetsSize) {
fileObject.put("message", "Exceeded project max asset size.\n"
+ "Please delete unused assets.\n\nContact WISE if your project needs more disk space.");
((ArrayList<HashMap<String, String>>) result.get("error")).add(fileObject);
boolean isSuccess = false;
if (!isUserAllowedToUpload(user, file, clamavClient)) {
fileObject.put("message", UPLOADING_THIS_FILE_NOT_ALLOWED_MESSAGE);
} else if (!isEnoughProjectDiskSpace(project, projectAssetsDir, file)) {
fileObject.put("message", EXCEEDED_MAX_PROJECT_SIZE_MESSAGE);
} else {
Path path = Paths.get(projectAssetsDir.getPath(), file.getOriginalFilename());
file.transferTo(path);
isSuccess = true;
}
if (isSuccess) {
((ArrayList<HashMap<String, String>>) result.get("success")).add(fileObject);
} else {
((ArrayList<HashMap<String, String>>) result.get("error")).add(fileObject);
}
}

private boolean isUserAllowedToUpload(User user, MultipartFile file) {
String allowedTypes = appProperties.getProperty("normalAuthorAllowedProjectAssetContentTypes");
if (user.isTrustedAuthor()) {
allowedTypes += ","
+ appProperties.getProperty("trustedAuthorAllowedProjectAssetContentTypes");
private boolean isUserAllowedToUpload(User user, MultipartFile file, ClamavClient clamavClient)
throws IOException {
if (clamavClient == null || isScanOk(clamavClient, file)) {
String allowedTypes = appProperties
.getProperty("normalAuthorAllowedProjectAssetContentTypes");
if (user.isTrustedAuthor()) {
allowedTypes += ","
+ appProperties.getProperty("trustedAuthorAllowedProjectAssetContentTypes");
}
try {
return allowedTypes.contains(getRealMimeType(file));
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}

private boolean isScanOk(ClamavClient clamavClient, MultipartFile file) throws IOException {
try {
return allowedTypes.contains(getRealMimeType(file));
} catch (IOException e) {
return false;
return clamavClient.scan(file.getInputStream()) instanceof ScanResult.OK;
} catch (ClamavException e) {
e.printStackTrace();
// There was an exception which could be caused by not being able to connect to the Clam AV
// server so we will say the scan is OK to prevent blocking the author from uploading
return true;
}
}

private boolean isEnoughProjectDiskSpace(Project project, File projectAssetsDir,
MultipartFile file) {
long sizeOfAssetsDirectory = FileUtils.sizeOfDirectory(projectAssetsDir);
Long projectMaxTotalAssetsSize = project.getMaxTotalAssetsSize();
if (projectMaxTotalAssetsSize == null) {
projectMaxTotalAssetsSize = Long
.parseLong(appProperties.getProperty("project_max_total_assets_size", "15728640"));
}
return sizeOfAssetsDirectory + file.getSize() < projectMaxTotalAssetsSize;
}

private String getRealMimeType(MultipartFile file) throws IOException {
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-dockerdev-sample.properties
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,6 @@ google.tokens.dir=

# backwards compatibility purpose only.
system-wide-salt=secret

# IP Address of Clam AV server for virus scanning
# clamav.server.address=127.0.0.1
3 changes: 3 additions & 0 deletions src/main/resources/application_sample.properties
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,6 @@ google.tokens.dir=

# backwards compatibility purpose only.
system-wide-salt=secret

# IP Address of Clam AV server for virus scanning
# clamav.server.address=127.0.0.1

0 comments on commit 208558d

Please sign in to comment.