-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
π Add antivirus inside Twake Drive (#725)
π Add antivirus inside Twake Drive (#725)
- Loading branch information
1 parent
cc53cd9
commit 00f819f
Showing
49 changed files
with
1,341 additions
and
257 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
.../core/platform/services/email-pusher/templates/en/notification-document-av-scan-alert.eta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<% layout('./_structure') %> | ||
<% it.title = 'Twake Drive Antivirus Alert' %> | ||
|
||
<%~ includeFile("../common/_body.eta", { | ||
paragraphs: [ | ||
` | ||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%" > | ||
<tbody> | ||
<tr> | ||
<td align="center" style="font-size:0px;padding:0 0 8px;word-break:break-word;"> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:24px;font-weight:800;line-height:29px;text-align:center;color:#FF0000;"> | ||
Important: Antivirus Alert on Your Twake Drive | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
`, | ||
` | ||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%" > | ||
<tbody> | ||
<tr> | ||
<td align="center" style="font-size:0px;padding:0 0 16px;word-break:break-word;" > | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="font-weight: 500">Antivirus scan for a file on your Twake Drive has flagged an issue.</span> | ||
</div> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="font-weight: 500"> | ||
File: ${it.notifications[0].item.name} | ||
</span> | ||
</div> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="color: #FF0000; font-weight: 600;"> | ||
Issue: ${ it.notifications[0].item.av_status === "scan_failed" ? "Scan Failed π" : it.notifications[0].item.av_status === "malicious" ? "Malicious Content Detected β οΈ" : "File too large to be scanned π«" } | ||
</span> | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;"> | ||
<a class="main-button" href="${it.encodedUrl}"> | ||
View File Details | ||
</a> | ||
</div> | ||
` | ||
] | ||
}) %> |
1 change: 1 addition & 0 deletions
1
...atform/services/email-pusher/templates/en/notification-document-av-scan-alert.subject.eta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Twake Drive Antivirus Alert |
48 changes: 48 additions & 0 deletions
48
.../core/platform/services/email-pusher/templates/fr/notification-document-av-scan-alert.eta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<% layout('./_structure') %> | ||
<% it.title = 'Alerte Antivirus dans votre Twake Drive' %> | ||
|
||
<%~ includeFile("../common/_body.eta", { | ||
paragraphs: [ | ||
` | ||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%" > | ||
<tbody> | ||
<tr> | ||
<td align="center" style="font-size:0px;padding:0 0 8px;word-break:break-word;"> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:24px;font-weight:800;line-height:29px;text-align:center;color:#FF0000;"> | ||
Important: Alerte Antivirus dans votre Twake Drive | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
`, | ||
` | ||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%" > | ||
<tbody> | ||
<tr> | ||
<td align="center" style="font-size:0px;padding:0 0 16px;word-break:break-word;" > | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="font-weight: 500">L'analyse antivirus d'un fichier dans votre Twake Drive a signalé un problème.</span> | ||
</div> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="font-weight: 500"> | ||
Fichier: ${it.notifications[0].item.name} | ||
</span> | ||
</div> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:16px;line-height:29px;text-align:center;color:#000000;"> | ||
<span style="color: #FF0000; font-weight: 600;"> | ||
ProblΓ¨me: ${ it.notifications[0].item.av_status === "scan_failed" ? "Γchec de l'analyse π" : it.notifications[0].item.av_status === "malicious" ? "Contenu malveillant dΓ©tectΓ© β οΈ" : "Fichier trop volumineux pour Γͺtre analysΓ© π«" } | ||
</span> | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;"> | ||
<a class="main-button" href="${it.encodedUrl}"> | ||
Voir sur Twake Drive | ||
</a> | ||
</div> | ||
` | ||
] | ||
}) %> |
1 change: 1 addition & 0 deletions
1
...atform/services/email-pusher/templates/fr/notification-document-av-scan-alert.subject.eta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Alerte Antivirus dans votre Twake Drive |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { TdriveService } from "../../core/platform/framework"; | ||
|
||
export default class AVService extends TdriveService<undefined> { | ||
version = "1"; | ||
name = "antivirus"; | ||
|
||
public async doInit(): Promise<this> { | ||
return this; | ||
} | ||
|
||
// TODO: remove | ||
api(): undefined { | ||
return undefined; | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
tdrive/backend/node/src/services/av/service/av-exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
export class AVException extends Error { | ||
constructor(readonly details: string, readonly status: number) { | ||
super(); | ||
this.message = details; | ||
} | ||
|
||
static initializationFailed(details: string): AVException { | ||
return new AVException(details, 503); | ||
} | ||
|
||
static fileNotFound(details: string): AVException { | ||
return new AVException(details, 404); | ||
} | ||
|
||
static scanFailed(details: string): AVException { | ||
return new AVException(details, 500); | ||
} | ||
|
||
static handleError(cause: Error, newException: AVException): void { | ||
throw cause instanceof AVException ? cause : newException; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { Initializable, TdriveServiceProvider } from "../../../core/platform/framework"; | ||
import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework"; | ||
import NodeClam from "clamscan"; | ||
import { AVStatus, DriveFile } from "src/services/documents/entities/drive-file"; | ||
import { FileVersion } from "src/services/documents/entities/file-version"; | ||
import { DriveExecutionContext } from "src/services/documents/types"; | ||
import globalResolver from "../../../services/global-resolver"; | ||
import { getFilePath } from "../../files/services"; | ||
import { getConfigOrDefault } from "../../../utils/get-config"; | ||
import { AVException } from "./av-exception"; | ||
|
||
export class AVServiceImpl implements TdriveServiceProvider, Initializable { | ||
version: "1"; | ||
av: NodeClam = null; | ||
logger: TdriveLogger = getLogger("Antivirus Service"); | ||
avEnabled = getConfigOrDefault("drive.featureAntivirus", false); | ||
private MAX_FILE_SIZE = getConfigOrDefault("av.maxFileSize", 26214400); // 25 MB | ||
|
||
async init(): Promise<this> { | ||
try { | ||
if (this.avEnabled) { | ||
this.av = await new NodeClam().init({ | ||
removeInfected: false, // Do not remove infected files | ||
quarantineInfected: false, // Do not quarantine, just alert | ||
scanLog: null, // No log file for this test | ||
debugMode: getConfigOrDefault("av.debugMode", false), // Enable debug messages | ||
clamdscan: { | ||
host: getConfigOrDefault("av.host", "localhost"), // IP of the server | ||
port: getConfigOrDefault("av.port", 3310) as number, // ClamAV server port | ||
timeout: getConfigOrDefault("av.timeout", 2000), // Timeout for scans | ||
localFallback: true, // Use local clamscan if needed | ||
}, | ||
}); | ||
} | ||
} catch (error) { | ||
logger.error({ error: `${error}` }, "Error while initializing Antivirus Service"); | ||
throw AVException.initializationFailed("Failed to initialize Antivirus service"); | ||
} | ||
return this; | ||
} | ||
|
||
async scanDocument( | ||
item: Partial<DriveFile>, | ||
version: Partial<FileVersion>, | ||
onScanComplete: (status: AVStatus) => Promise<void>, | ||
context: DriveExecutionContext, | ||
): Promise<AVStatus> { | ||
try { | ||
// get the file from the storage | ||
const file = await globalResolver.services.files.get( | ||
version.file_metadata.external_id, | ||
context, | ||
); | ||
|
||
if (!file) { | ||
this.logger.error(`File ${version.file_metadata.external_id} not found`); | ||
throw AVException.fileNotFound(`File ${version.file_metadata.external_id} not found`); | ||
} | ||
// check if the file is too large | ||
if (file.upload_data.size > this.MAX_FILE_SIZE) { | ||
this.logger.info( | ||
`File ${file.id} is too large (${file.upload_data.size} bytes) to be scanned. Skipping...`, | ||
); | ||
return "skipped"; | ||
} | ||
|
||
// read the file from the storage | ||
const readableStream = await globalResolver.platformServices.storage.read(getFilePath(file), { | ||
totalChunks: file.upload_data.chunks, | ||
encryptionAlgo: globalResolver.services.files.getEncryptionAlgorithm(), | ||
encryptionKey: file.encryption_key, | ||
}); | ||
|
||
// scan the file | ||
this.av.scanStream(readableStream, async (err, { isInfected, viruses }) => { | ||
if (err) { | ||
await onScanComplete("scan_failed"); | ||
this.logger.error(`Scan failed for item ${item.id} due to error: ${err.message}`); | ||
} else if (isInfected) { | ||
await onScanComplete("malicious"); | ||
this.logger.info(`Item ${item.id} is malicious. Viruses found: ${viruses.join(", ")}`); | ||
} else { | ||
await onScanComplete("safe"); | ||
this.logger.info(`Item ${item.id} is safe with no viruses detected.`); | ||
} | ||
}); | ||
|
||
return "scanning"; | ||
} catch (error) { | ||
// mark the file as failed to scan | ||
await onScanComplete("scan_failed"); | ||
|
||
// log the error | ||
this.logger.error(`Error scanning file ${item.last_version_cache.file_metadata.external_id}`); | ||
throw AVException.scanFailed("Document scanning encountered an error"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.