Adjust antivirus feature
This commit is contained in:
@@ -128,14 +128,25 @@
|
|||||||
"featureManageAccess": true,
|
"featureManageAccess": true,
|
||||||
"defaultCompany": "00000000-0000-4000-0000-000000000000",
|
"defaultCompany": "00000000-0000-4000-0000-000000000000",
|
||||||
"defaultUserQuota": 200000000,
|
"defaultUserQuota": 200000000,
|
||||||
"featureAntivirus": false
|
"featureAntivirus": false,
|
||||||
|
"featureAvStatusAllowed": {
|
||||||
|
"download": ["uploaded", "safe", "scanning", "scan_failed", "malicious"],
|
||||||
|
"share": ["uploaded", "safe", "scanning", "skipped"],
|
||||||
|
"rename": ["uploaded", "safe", "scanning"],
|
||||||
|
"move": ["uploaded", "safe", "scanning"],
|
||||||
|
"rescan": ["scan_failed"],
|
||||||
|
"copy_link": ["uploaded", "safe", "scanning"],
|
||||||
|
"manage_access": ["uploaded", "safe", "scanning", "skipped"],
|
||||||
|
"version": ["uploaded", "safe", "scanning"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"av": {
|
"av": {
|
||||||
"host": "av",
|
"host": "av",
|
||||||
"port": 3310,
|
"port": 3310,
|
||||||
"debugMode": false,
|
"debugMode": false,
|
||||||
"timeout": 2000,
|
"timeout": 2000,
|
||||||
"maxFileSize": 26214400
|
"maxFileSize": 4294967295,
|
||||||
|
"deleteInfectedFiles": false
|
||||||
},
|
},
|
||||||
"applications": {
|
"applications": {
|
||||||
"grid": [
|
"grid": [
|
||||||
|
|||||||
@@ -149,13 +149,18 @@
|
|||||||
"featureDisplayEmail": "ENABLE_FEATURE_DISPLAY_EMAIL",
|
"featureDisplayEmail": "ENABLE_FEATURE_DISPLAY_EMAIL",
|
||||||
"featureUserQuota": "ENABLE_FEATURE_USER_QUOTA",
|
"featureUserQuota": "ENABLE_FEATURE_USER_QUOTA",
|
||||||
"featureManageAccess": "ENABLE_FEATURE_MANAGE_ACCESS",
|
"featureManageAccess": "ENABLE_FEATURE_MANAGE_ACCESS",
|
||||||
"featureAntivirus": "ENABLE_FEATURE_ANTIVIRUS"
|
"featureAntivirus": "ENABLE_FEATURE_ANTIVIRUS",
|
||||||
|
"featureAvStatusAllowed": {
|
||||||
|
"__name": "FEATURE_AV_STATUS_ALLOWED",
|
||||||
|
"__format": "json"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"av": {
|
"av": {
|
||||||
"host": "AV_HOST",
|
"host": "AV_HOST",
|
||||||
"port": "AV_PORT",
|
"port": "AV_PORT",
|
||||||
"debugMode": "AV_DEBUG_MODE",
|
"debugMode": "AV_DEBUG_MODE",
|
||||||
"timeout": "AV_TIMEOUT",
|
"timeout": "AV_TIMEOUT",
|
||||||
"maxFileSize": "AV_MAX_FILE_SIZE"
|
"maxFileSize": "AV_MAX_FILE_SIZE",
|
||||||
|
"deleteInfectedFiles": "AV_DELETE_INFECTED_FILES"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,13 +149,18 @@
|
|||||||
"featureDisplayEmail": "ENABLE_FEATURE_DISPLAY_EMAIL",
|
"featureDisplayEmail": "ENABLE_FEATURE_DISPLAY_EMAIL",
|
||||||
"featureUserQuota": "ENABLE_FEATURE_USER_QUOTA",
|
"featureUserQuota": "ENABLE_FEATURE_USER_QUOTA",
|
||||||
"featureManageAccess": "ENABLE_FEATURE_MANAGE_ACCESS",
|
"featureManageAccess": "ENABLE_FEATURE_MANAGE_ACCESS",
|
||||||
"featureAntivirus": "ENABLE_FEATURE_ANTIVIRUS"
|
"featureAntivirus": "ENABLE_FEATURE_ANTIVIRUS",
|
||||||
|
"featureAvStatusAllowed": {
|
||||||
|
"__name": "FEATURE_AV_STATUS_ALLOWED",
|
||||||
|
"__format": "json"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"av": {
|
"av": {
|
||||||
"host": "AV_HOST",
|
"host": "AV_HOST",
|
||||||
"port": "AV_PORT",
|
"port": "AV_PORT",
|
||||||
"debugMode": "AV_DEBUG_MODE",
|
"debugMode": "AV_DEBUG_MODE",
|
||||||
"timeout": "AV_TIMEOUT",
|
"timeout": "AV_TIMEOUT",
|
||||||
"maxFileSize": "AV_MAX_FILE_SIZE"
|
"maxFileSize": "AV_MAX_FILE_SIZE",
|
||||||
|
"deleteInfectedFiles": "AV_DELETE_INFECTED_FILES"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,14 +129,25 @@
|
|||||||
"featureManageAccess": true,
|
"featureManageAccess": true,
|
||||||
"defaultCompany": "00000000-0000-4000-0000-000000000000",
|
"defaultCompany": "00000000-0000-4000-0000-000000000000",
|
||||||
"defaultUserQuota": 200000000,
|
"defaultUserQuota": 200000000,
|
||||||
"featureAntivirus": false
|
"featureAntivirus": false,
|
||||||
|
"featureAvStatusAllowed": {
|
||||||
|
"download": ["uploaded", "safe", "scanning", "scan_failed", "malicious"],
|
||||||
|
"share": ["uploaded", "safe", "scanning", "skipped"],
|
||||||
|
"rename": ["uploaded", "safe", "scanning"],
|
||||||
|
"move": ["uploaded", "safe", "scanning"],
|
||||||
|
"rescan": ["scan_failed"],
|
||||||
|
"copy_link": ["uploaded", "safe", "scanning"],
|
||||||
|
"manage_access": ["uploaded", "safe", "scanning", "skipped"],
|
||||||
|
"version": ["uploaded", "safe", "scanning"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"av": {
|
"av": {
|
||||||
"host": "av",
|
"host": "av",
|
||||||
"port": 3310,
|
"port": 3310,
|
||||||
"debugMode": false,
|
"debugMode": false,
|
||||||
"timeout": 2000,
|
"timeout": 2000,
|
||||||
"maxFileSize": 26214400
|
"maxFileSize": 4294967295,
|
||||||
|
"deleteInfectedFiles": false
|
||||||
},
|
},
|
||||||
"applications": {
|
"applications": {
|
||||||
"grid": [
|
"grid": [
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<% layout('./_structure') %>
|
||||||
|
<% it.title = 'Twake Drive Infected Document Deletion 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">A file on your Twake Drive was flagged during an antivirus scan and has been deleted to protect your account.</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>
|
||||||
|
`
|
||||||
|
]
|
||||||
|
}) %>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Twake Drive Infected Document Deletion Alert
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<% layout('./_structure') %>
|
||||||
|
<% it.title = 'Alerte de suppression de document infecté sur 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 sur 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">Un fichier sur votre Twake Drive a été signalé lors d’une analyse antivirus et a été supprimé afin de protéger votre compte.</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>
|
||||||
|
`
|
||||||
|
]
|
||||||
|
}) %>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Alerte de suppression de document infecté sur Twake Drive
|
||||||
@@ -3,7 +3,7 @@ import { getLogger, logger, TdriveLogger } from "../../../core/platform/framewor
|
|||||||
import NodeClam from "clamscan";
|
import NodeClam from "clamscan";
|
||||||
import { AVStatus, DriveFile } from "src/services/documents/entities/drive-file";
|
import { AVStatus, DriveFile } from "src/services/documents/entities/drive-file";
|
||||||
import { FileVersion } from "src/services/documents/entities/file-version";
|
import { FileVersion } from "src/services/documents/entities/file-version";
|
||||||
import { DriveExecutionContext } from "src/services/documents/types";
|
import { DriveExecutionContext, NotificationActionType } from "../../documents/types";
|
||||||
import globalResolver from "../../../services/global-resolver";
|
import globalResolver from "../../../services/global-resolver";
|
||||||
import { getFilePath } from "../../files/services";
|
import { getFilePath } from "../../files/services";
|
||||||
import { getConfigOrDefault } from "../../../utils/get-config";
|
import { getConfigOrDefault } from "../../../utils/get-config";
|
||||||
@@ -14,7 +14,8 @@ export class AVServiceImpl implements TdriveServiceProvider, Initializable {
|
|||||||
av: NodeClam = null;
|
av: NodeClam = null;
|
||||||
logger: TdriveLogger = getLogger("Antivirus Service");
|
logger: TdriveLogger = getLogger("Antivirus Service");
|
||||||
avEnabled: boolean = getConfigOrDefault<boolean>("drive.featureAntivirus", false);
|
avEnabled: boolean = getConfigOrDefault<boolean>("drive.featureAntivirus", false);
|
||||||
private MAX_FILE_SIZE: number = getConfigOrDefault<number>("av.maxFileSize", 26214400); // 25 MB
|
deleteInfectedFileEnabled: boolean = getConfigOrDefault<boolean>("av.deleteInfectedFiles", false);
|
||||||
|
private MAX_FILE_SIZE: number = getConfigOrDefault<number>("av.maxFileSize", 4294967295); // 4GB
|
||||||
|
|
||||||
async init(): Promise<this> {
|
async init(): Promise<this> {
|
||||||
try {
|
try {
|
||||||
@@ -56,6 +57,7 @@ export class AVServiceImpl implements TdriveServiceProvider, Initializable {
|
|||||||
this.logger.error(`File ${version.file_metadata.external_id} not found`);
|
this.logger.error(`File ${version.file_metadata.external_id} not found`);
|
||||||
throw AVException.fileNotFound(`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
|
// check if the file is too large
|
||||||
if (file.upload_data.size > this.MAX_FILE_SIZE) {
|
if (file.upload_data.size > this.MAX_FILE_SIZE) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -79,6 +81,11 @@ export class AVServiceImpl implements TdriveServiceProvider, Initializable {
|
|||||||
} else if (isInfected) {
|
} else if (isInfected) {
|
||||||
await onScanComplete("malicious");
|
await onScanComplete("malicious");
|
||||||
this.logger.info(`Item ${item.id} is malicious. Viruses found: ${viruses.join(", ")}`);
|
this.logger.info(`Item ${item.id} is malicious. Viruses found: ${viruses.join(", ")}`);
|
||||||
|
|
||||||
|
// Delete infected files if feature flag is enabled
|
||||||
|
if (this.deleteInfectedFileEnabled) {
|
||||||
|
await this.deleteInfectedFile(item, context);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await onScanComplete("safe");
|
await onScanComplete("safe");
|
||||||
this.logger.info(`Item ${item.id} is safe with no viruses detected.`);
|
this.logger.info(`Item ${item.id} is safe with no viruses detected.`);
|
||||||
@@ -95,4 +102,27 @@ export class AVServiceImpl implements TdriveServiceProvider, Initializable {
|
|||||||
throw AVException.scanFailed("Document scanning encountered an error");
|
throw AVException.scanFailed("Document scanning encountered an error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteInfectedFile(item: Partial<DriveFile>, context: DriveExecutionContext) {
|
||||||
|
try {
|
||||||
|
// Delete infected file for permanent
|
||||||
|
await globalResolver.services.documents.documents.delete(item.id, null, context, true);
|
||||||
|
this.logger.info(`Infected file ${item.id} was automatically deleted`);
|
||||||
|
|
||||||
|
// Send notification to user about the file deletion
|
||||||
|
globalResolver.services.documents.engine.notifyInfectedDocumentRemoved({
|
||||||
|
context,
|
||||||
|
item: {
|
||||||
|
...item,
|
||||||
|
company_id: context.company.id,
|
||||||
|
} as DriveFile,
|
||||||
|
type: NotificationActionType.DIRECT,
|
||||||
|
notificationReceiver: context.user.id,
|
||||||
|
notificationEmitter: "",
|
||||||
|
});
|
||||||
|
this.logger.info(`Sent notification to ${context.user.id} about file deletion`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to delete infected file ${item.id}: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ export class DocumentsEngine implements Initializable {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
localEventBus.subscribe(
|
||||||
|
DocumentEvents.INFECTED_DOCUMENT_REMOVED,
|
||||||
|
async (e: NotificationPayloadType) => {
|
||||||
|
await this.DispatchDocumentEvent(e, DocumentEvents.INFECTED_DOCUMENT_REMOVED);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,4 +104,8 @@ export class DocumentsEngine implements Initializable {
|
|||||||
notifyDocumentAVScanAlert(notificationPayload: NotificationPayloadType) {
|
notifyDocumentAVScanAlert(notificationPayload: NotificationPayloadType) {
|
||||||
localEventBus.publish(DocumentEvents.DOCUMENT_AV_SCAN_ALERT, notificationPayload);
|
localEventBus.publish(DocumentEvents.DOCUMENT_AV_SCAN_ALERT, notificationPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyInfectedDocumentRemoved(notificationPayload: NotificationPayloadType) {
|
||||||
|
localEventBus.publish(DocumentEvents.INFECTED_DOCUMENT_REMOVED, notificationPayload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -718,6 +718,7 @@ export class DocumentsService {
|
|||||||
id: string | RootType | TrashType,
|
id: string | RootType | TrashType,
|
||||||
item?: DriveFile,
|
item?: DriveFile,
|
||||||
context?: DriveExecutionContext,
|
context?: DriveExecutionContext,
|
||||||
|
isPermanentlyDelete?: boolean,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
//We can't remove the root folder
|
//We can't remove the root folder
|
||||||
@@ -864,6 +865,10 @@ export class DocumentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.update(item.id, item, context);
|
await this.update(item.id, item, context);
|
||||||
|
|
||||||
|
if (isPermanentlyDelete) {
|
||||||
|
await this.delete(item.id, item, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await updateItemSize(previousParentId, this.repository, context);
|
await updateItemSize(previousParentId, this.repository, context);
|
||||||
}
|
}
|
||||||
@@ -1833,7 +1838,10 @@ export class DocumentsService {
|
|||||||
item.av_status = status;
|
item.av_status = status;
|
||||||
await this.repository.save(item);
|
await this.repository.save(item);
|
||||||
|
|
||||||
if (["malicious", "scan_failed"].includes(status)) {
|
if (
|
||||||
|
(!globalResolver.services.av?.deleteInfectedFileEnabled && status === "malicious") ||
|
||||||
|
status === "scan_failed"
|
||||||
|
) {
|
||||||
await this.notifyAVScanAlert(item, context);
|
await this.notifyAVScanAlert(item, context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,12 +123,14 @@ export enum DocumentEvents {
|
|||||||
DOCUMENT_SAHRED = "document_shared",
|
DOCUMENT_SAHRED = "document_shared",
|
||||||
DOCUMENT_VERSION_UPDATED = "document_version_updated",
|
DOCUMENT_VERSION_UPDATED = "document_version_updated",
|
||||||
DOCUMENT_AV_SCAN_ALERT = "document_av_scan_alert",
|
DOCUMENT_AV_SCAN_ALERT = "document_av_scan_alert",
|
||||||
|
INFECTED_DOCUMENT_REMOVED = "infected_document_removed",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const eventToTemplateMap: Record<string, any> = {
|
export const eventToTemplateMap: Record<string, any> = {
|
||||||
[DocumentEvents.DOCUMENT_AV_SCAN_ALERT]: "notification-document-av-scan-alert",
|
[DocumentEvents.DOCUMENT_AV_SCAN_ALERT]: "notification-document-av-scan-alert",
|
||||||
[DocumentEvents.DOCUMENT_VERSION_UPDATED]: "notification-document-version-updated",
|
[DocumentEvents.DOCUMENT_VERSION_UPDATED]: "notification-document-version-updated",
|
||||||
[DocumentEvents.DOCUMENT_SAHRED]: "notification-document-shared",
|
[DocumentEvents.DOCUMENT_SAHRED]: "notification-document-shared",
|
||||||
|
[DocumentEvents.INFECTED_DOCUMENT_REMOVED]: "notification-infected-document-deletion-alert",
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum NotificationActionType {
|
export enum NotificationActionType {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ export function formatCompany(
|
|||||||
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]: JSON.parse(
|
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]: JSON.parse(
|
||||||
config.get("drive.featureAntivirus") || "false",
|
config.get("drive.featureAntivirus") || "false",
|
||||||
),
|
),
|
||||||
|
[CompanyFeaturesEnum.COMPANY_AV_STATUS_ALLOWED]:
|
||||||
|
config.get("drive.featureAvStatusAllowed") || {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(res.plan?.features || {}),
|
...(res.plan?.features || {}),
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const companyObjectSchema = {
|
|||||||
[CompanyFeaturesEnum.COMPANY_USER_QUOTA]: { type: "boolean" },
|
[CompanyFeaturesEnum.COMPANY_USER_QUOTA]: { type: "boolean" },
|
||||||
[CompanyFeaturesEnum.COMPANY_MANAGE_ACCESS]: { type: "boolean" },
|
[CompanyFeaturesEnum.COMPANY_MANAGE_ACCESS]: { type: "boolean" },
|
||||||
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]: { type: "boolean" },
|
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]: { type: "boolean" },
|
||||||
|
[CompanyFeaturesEnum.COMPANY_AV_STATUS_ALLOWED]: {} as { [key: string]: string[] },
|
||||||
guests: { type: "number" }, // to rename or delete
|
guests: { type: "number" }, // to rename or delete
|
||||||
members: { type: "number" }, // to rename or delete
|
members: { type: "number" }, // to rename or delete
|
||||||
storage: { type: "number" }, // to rename or delete
|
storage: { type: "number" }, // to rename or delete
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export enum CompanyFeaturesEnum {
|
|||||||
COMPANY_USER_QUOTA = "company:user_quota",
|
COMPANY_USER_QUOTA = "company:user_quota",
|
||||||
COMPANY_MANAGE_ACCESS = "company:managed_access",
|
COMPANY_MANAGE_ACCESS = "company:managed_access",
|
||||||
COMPANY_AV_ENABLED = "company:av_enabled",
|
COMPANY_AV_ENABLED = "company:av_enabled",
|
||||||
|
COMPANY_AV_STATUS_ALLOWED = "company:av_status_allowed",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyFeaturesObject = {
|
export type CompanyFeaturesObject = {
|
||||||
@@ -102,6 +103,7 @@ export type CompanyFeaturesObject = {
|
|||||||
[CompanyFeaturesEnum.COMPANY_USER_QUOTA]?: boolean;
|
[CompanyFeaturesEnum.COMPANY_USER_QUOTA]?: boolean;
|
||||||
[CompanyFeaturesEnum.COMPANY_MANAGE_ACCESS]?: boolean;
|
[CompanyFeaturesEnum.COMPANY_MANAGE_ACCESS]?: boolean;
|
||||||
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]?: boolean;
|
[CompanyFeaturesEnum.COMPANY_AV_ENABLED]?: boolean;
|
||||||
|
[CompanyFeaturesEnum.COMPANY_AV_STATUS_ALLOWED]?: { [key: string]: string[] };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CompanyLimitsObject = {
|
export type CompanyLimitsObject = {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ export enum FeatureNames {
|
|||||||
COMPANY_USER_QUOTA = 'company:user_quota',
|
COMPANY_USER_QUOTA = 'company:user_quota',
|
||||||
COMPANY_MANAGE_ACCESS = 'company:managed_access',
|
COMPANY_MANAGE_ACCESS = 'company:managed_access',
|
||||||
COMPANY_AV_ENABLED = 'company:av_enabled',
|
COMPANY_AV_ENABLED = 'company:av_enabled',
|
||||||
|
COMPANY_AV_STATUS_ALLOWED = 'company:av_status_allowed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeatureValueType = boolean | number;
|
export type FeatureValueType = boolean | number | { [key: string]: string[] };
|
||||||
|
|
||||||
const availableFeaturesWithDefaults = new Map<FeatureNames, any>();
|
const availableFeaturesWithDefaults = new Map<FeatureNames, any>();
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ availableFeaturesWithDefaults.set(FeatureNames.COMPANY_DISPLAY_EMAIL, true);
|
|||||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_USER_QUOTA, false);
|
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_USER_QUOTA, false);
|
||||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_MANAGE_ACCESS, true);
|
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_MANAGE_ACCESS, true);
|
||||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_AV_ENABLED, false);
|
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_AV_ENABLED, false);
|
||||||
|
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_AV_STATUS_ALLOWED, {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChannelServiceImpl that allow you to manage feature flipping in Tdrive using react feature toggles
|
* ChannelServiceImpl that allow you to manage feature flipping in Tdrive using react feature toggles
|
||||||
|
|||||||
@@ -70,6 +70,17 @@ export const useOnBuildContextMenu = (
|
|||||||
!item?.is_directory &&
|
!item?.is_directory &&
|
||||||
(item?.av_status || '').length > 0 &&
|
(item?.av_status || '').length > 0 &&
|
||||||
!['uploaded', 'scanning', 'safe'].includes(item?.av_status || '');
|
!['uploaded', 'scanning', 'safe'].includes(item?.av_status || '');
|
||||||
|
|
||||||
|
const avStatusAllowed: { [key: string]: string[] } = FeatureTogglesService.getFeatureValue(FeatureNames.COMPANY_AV_STATUS_ALLOWED);
|
||||||
|
const isCheckFileActionByAvStatus = !item?.is_directory && (item?.av_status || '').length > 0;
|
||||||
|
const isAllowToShare = isCheckFileActionByAvStatus && avStatusAllowed['share']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToManageAccess = isCheckFileActionByAvStatus && avStatusAllowed['manage_access']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToRescan = isCheckFileActionByAvStatus && avStatusAllowed['rescan']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToDownload = isCheckFileActionByAvStatus && avStatusAllowed['download']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToMove = isCheckFileActionByAvStatus && avStatusAllowed['move']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToRename = isCheckFileActionByAvStatus && avStatusAllowed['rename']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToCopyLink = isCheckFileActionByAvStatus && avStatusAllowed['copy_link']?.includes(item?.av_status as string);
|
||||||
|
const isAllowToCreateVersion = isCheckFileActionByAvStatus && avStatusAllowed['version']?.includes(item?.av_status as string);
|
||||||
|
|
||||||
let menu: any[] = [];
|
let menu: any[] = [];
|
||||||
|
|
||||||
@@ -89,7 +100,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'share-alt',
|
icon: 'share-alt',
|
||||||
text: Languages.t('components.item_context_menu.share'),
|
text: Languages.t('components.item_context_menu.share'),
|
||||||
hide: hideShareItem || notSafe,
|
hide: hideShareItem || !isAllowToShare,
|
||||||
onClick: () => setPublicLinkModalState({ open: true, id: item.id }),
|
onClick: () => setPublicLinkModalState({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -97,7 +108,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'users-alt',
|
icon: 'users-alt',
|
||||||
text: Languages.t('components.item_context_menu.manage_access'),
|
text: Languages.t('components.item_context_menu.manage_access'),
|
||||||
hide: hideManageAccessItem || notSafe,
|
hide: hideManageAccessItem || !isAllowToManageAccess,
|
||||||
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -105,7 +116,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'shield-check',
|
icon: 'shield-check',
|
||||||
text: Languages.t('components.item_context_menu.rescan_document'),
|
text: Languages.t('components.item_context_menu.rescan_document'),
|
||||||
hide: !(item.av_status === 'scan_failed'),
|
hide: !isAllowToRescan,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
reScan(item);
|
reScan(item);
|
||||||
},
|
},
|
||||||
@@ -122,12 +133,13 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'download-alt',
|
icon: 'download-alt',
|
||||||
text: Languages.t('components.item_context_menu.download'),
|
text: Languages.t('components.item_context_menu.download'),
|
||||||
|
hide: !isAllowToDownload,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (item && item.is_directory) {
|
if (item && item.is_directory) {
|
||||||
downloadZip([item!.id]);
|
downloadZip([item!.id]);
|
||||||
console.log(item!.id);
|
console.log(item!.id);
|
||||||
} else {
|
} else {
|
||||||
download(item.id, notSafe);
|
download(item.id, !isAllowToDownload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -149,7 +161,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'folder-question',
|
icon: 'folder-question',
|
||||||
text: Languages.t('components.item_context_menu.move'),
|
text: Languages.t('components.item_context_menu.move'),
|
||||||
hide: access === 'read' || inTrash || inPublicSharing || notSafe,
|
hide: access === 'read' || inTrash || inPublicSharing || !isAllowToMove,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
setSelectorModalState({
|
setSelectorModalState({
|
||||||
open: true,
|
open: true,
|
||||||
@@ -175,7 +187,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'file-edit-alt',
|
icon: 'file-edit-alt',
|
||||||
text: Languages.t('components.item_context_menu.rename'),
|
text: Languages.t('components.item_context_menu.rename'),
|
||||||
hide: access === 'read' || inTrash || notSafe,
|
hide: access === 'read' || inTrash || !isAllowToRename,
|
||||||
onClick: () => setPropertiesModalState({ open: true, id: item.id, inPublicSharing }),
|
onClick: () => setPropertiesModalState({ open: true, id: item.id, inPublicSharing }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -187,7 +199,7 @@ export const useOnBuildContextMenu = (
|
|||||||
!item.access_info.public?.level ||
|
!item.access_info.public?.level ||
|
||||||
item.access_info.public?.level === 'none' ||
|
item.access_info.public?.level === 'none' ||
|
||||||
inTrash ||
|
inTrash ||
|
||||||
notSafe,
|
!isAllowToCopyLink,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
copyToClipboard(getPublicLink(item || parent?.item));
|
copyToClipboard(getPublicLink(item || parent?.item));
|
||||||
ToasterService.success(
|
ToasterService.success(
|
||||||
@@ -200,7 +212,7 @@ export const useOnBuildContextMenu = (
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
icon: 'history',
|
icon: 'history',
|
||||||
text: Languages.t('components.item_context_menu.versions'),
|
text: Languages.t('components.item_context_menu.versions'),
|
||||||
hide: item.is_directory || inTrash || notSafe,
|
hide: item.is_directory || inTrash || !isAllowToCreateVersion,
|
||||||
onClick: () => setVersionModal({ open: true, id: item.id }),
|
onClick: () => setVersionModal({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{ type: 'separator', hide: access !== 'manage' || inTrash || notSafe },
|
{ type: 'separator', hide: access !== 'manage' || inTrash || notSafe },
|
||||||
|
|||||||
Reference in New Issue
Block a user