diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs index ae616adbb..83f486777 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs @@ -2963,6 +2963,40 @@ public async Task ImportExcel_EmptyFile_DoesNotImport_RedirectsToListWithError() _translationServiceMock.Verify(t => t.GetResource("Admin.Common.UploadFile"), Times.Once); } + // --- ImportExcel: global scope only --- + + [TestMethod] + public async Task ImportExcel_StoreScope_DeniesAndNeverCallsImportManager() + { + _scopeMock.Setup(s => s.DefaultStoreId).Returns("store-1"); + var fileMock = new Mock(); + fileMock.Setup(f => f.Length).Returns(100); + var importManagerMock = new Mock>(); + + var result = await _controller.ImportExcel(fileMock.Object, importManagerMock.Object); + + var redirect = Assert.IsInstanceOfType(result); + Assert.AreEqual("List", redirect.ActionName); + importManagerMock.Verify(i => i.Import(It.IsAny()), Times.Never); + _translationServiceMock.Verify(t => t.GetResource("Admin.AccessDenied.Title"), Times.Once); + } + + [TestMethod] + public async Task ImportExcel_VendorScope_DeniesAndNeverCallsImportManager() + { + _scopeMock.Setup(s => s.DefaultVendorId).Returns("vendor-1"); + var fileMock = new Mock(); + fileMock.Setup(f => f.Length).Returns(100); + var importManagerMock = new Mock>(); + + var result = await _controller.ImportExcel(fileMock.Object, importManagerMock.Object); + + var redirect = Assert.IsInstanceOfType(result); + Assert.AreEqual("List", redirect.ActionName); + importManagerMock.Verify(i => i.Import(It.IsAny()), Times.Never); + _translationServiceMock.Verify(t => t.GetResource("Admin.AccessDenied.Title"), Times.Once); + } + [TestMethod] public async Task ImportExcel_NullFile_DoesNotImport_RedirectsToListWithError() { diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs index 457b334a8..28c5c8b70 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs @@ -1853,13 +1853,8 @@ public async Task ExportExcelSelected(string selectedIds, return File(bytes, "text/xls", "products.xlsx"); } - // Not virtual: Vendor's original ProductController has no ImportExcel action at all, and Vendor is - // never granted the "Products" permission's Import action (grep across src/Web/Grand.Web.Vendor found - // no PermissionActionName.Import usage anywhere) - vendors are deliberately not allowed to bulk-import - // products. [PermissionAuthorizeAction(PermissionActionName.Import)] below already 403s for any host - // whose role has no Import grant for Products, so this is safe to expose unconditionally on the base - // class; Vendor's (future) subclass simply never routes a view to it, same as any other - // permission-gated action already in this file. + // Global scope only, as for brands, categories and collections: the importer is not store- or + // vendor-aware. // // Concern (flagged, not fixed - out of scope for this row): ImportExcel only checks // `importexcelfile.Length > 0` before handing the raw stream to IImportManager.Import. @@ -1875,6 +1870,12 @@ public async Task ImportExcel(IFormFile importexcelfile, { try { + if (Scope.DefaultStoreId is not null || Scope.DefaultVendorId is not null) + { + Error(translationService.GetResource("Admin.AccessDenied.Title")); + return RedirectToAction("List"); + } + if (importexcelfile is { Length: > 0 }) { await importManager.Import(importexcelfile.OpenReadStream());