diff --git a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Export/AssessmentExportManager.cs b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Export/AssessmentExportManager.cs index 7093652813..3c05cc1c35 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Export/AssessmentExportManager.cs +++ b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Export/AssessmentExportManager.cs @@ -4,7 +4,10 @@ // // //////////////////////////////// +using CSETWebCore.Business.AssessmentIO.Models.AutoGenerated; using CSETWebCore.DataLayer.Model; +using CSETWebCore.Helpers; +using CSETWebCore.Model.AssessmentIO; using Microsoft.EntityFrameworkCore; using Nelibur.ObjectMapper; using Newtonsoft.Json; @@ -15,13 +18,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using CSETWebCore.Model.AssessmentIO; -using CSETWebCore.Helpers; -using Ionic.Zip; -using Microsoft.IdentityModel.Tokens; -using CSETWebCore.Business.AssessmentIO.Models.AutoGenerated; -using System.Collections; -using System.Linq.Expressions; + //using CSETWebCore.Business.ImportAssessment.Models.Version_10_1; @@ -49,7 +46,6 @@ public AssessmentExportManager(CSETContext context) private void SetupBindings() { - TinyMapper.Bind(); TinyMapper.Bind(); TinyMapper.Bind(); @@ -390,7 +386,7 @@ join p in _context.PARAMETER_VALUES on an.Answer_Id equals p.Answer_Id } if (scrubData) - { + { model = RemovePcii(model); } return model; @@ -407,7 +403,7 @@ private UploadAssessmentModel RemovePcii(UploadAssessmentModel model) item.Issue = null; item.Impact = null; item.Recommendations = null; - item.Vulnerabilities = null; + item.Vulnerabilities = null; } foreach (var item in model.jANSWER) @@ -487,7 +483,7 @@ private UploadAssessmentModel RemovePcii(UploadAssessmentModel model) { item.PCII_Number = null; } - + return model; } @@ -499,16 +495,19 @@ private UploadAssessmentModel RemovePcii(UploadAssessmentModel model) /// private Stream ArchiveStream(int assessmentId, string password, string passwordHint, bool jsonOnly = false, bool scrubData = false) { - var archiveStream = new MemoryStream(); + var model = CopyForExport(assessmentId, scrubData); - using (var archive = new ZipFile()) + + var archiveStream = new MemoryStream(); + using (var zipWrapper = new SharpZipLibWrapper(archiveStream)) { - if (password != null && password != "") + if (!string.IsNullOrEmpty(password)) { - archive.Password = password; + zipWrapper.Password = password; } + foreach (var standard in model.jAVAILABLE_STANDARDS) { if (!standard.Selected) @@ -551,7 +550,10 @@ join nqs in _context.NEW_QUESTION_SETS on nq.Question_Id equals nqs.Question_Id //var standardEntry = archive.CreateEntry($"{setname}.json"); var jsonStandard = JsonConvert.SerializeObject(extStandard, Formatting.Indented); - ZipEntry standardEntry = archive.AddEntry($"{setname}.json", jsonStandard); + ////////////////ZipEntry standardEntry = archive.Add$"{setname}.json", jsonStandard); + + zipWrapper.AddEntry($"{setname}.json", jsonStandard); + //Set the GUID at time of export so we are sure it's right!!! @@ -576,7 +578,7 @@ join nqs in _context.NEW_QUESTION_SETS on nq.Question_Id equals nqs.Question_Id })).ToList(); model.CustomStandards.Add(extStandard.shortName); - + @@ -594,14 +596,14 @@ join nqs in _context.NEW_QUESTION_SETS on nq.Question_Id equals nqs.Question_Id var doc = genFile.ToExternalDocument(); var jsonDoc = JsonConvert.SerializeObject(doc, Formatting.Indented); - ZipEntry docEntry = archive.AddEntry($"{doc.ShortName}.json", jsonDoc); - + zipWrapper.AddEntry($"{doc.ShortName}.json", jsonDoc); model.CustomStandardDocs.Add(file.fileName); } } } + model.ExportDateTime = DateTime.UtcNow; var json = JsonConvert.SerializeObject(model, Formatting.Indented); @@ -615,19 +617,20 @@ join nqs in _context.NEW_QUESTION_SETS on nq.Question_Id equals nqs.Question_Id } else { - // Write the ZIP file with the JSON and any artifacts attached. - ZipEntry jsonEntry = archive.AddEntry("model.json", json); - ZipEntry hint = archive.AddEntry($"{passwordHint}.hint", passwordHint); - archive.Save(archiveStream); + zipWrapper.AddEntry("model.json", json); + zipWrapper.AddEntry($"{passwordHint}.hint", passwordHint); } - } + zipWrapper.Save(); + zipWrapper.CloseStream(); + } archiveStream.Seek(0, SeekOrigin.Begin); return archiveStream; } + /// /// Export an assessment by its ID. /// Can optionally provide a password and password hint that will be used during import process. @@ -667,9 +670,6 @@ public AssessmentExportFile ExportAssessment(int assessmentId, string fileExtens /// public MemoryStream BulkExportAssessments(Guid[] guids, string fileExtension) { - - var archiveStream = new MemoryStream(); - var exportAssessments = _context.ASSESSMENTS.Where(a => guids.Contains(a.Assessment_GUID)).ToList(); // Assessments with provided guids do not exist. @@ -679,7 +679,8 @@ public MemoryStream BulkExportAssessments(Guid[] guids, string fileExtension) } // Zip all the assessments into one archive. - using (var archive = new ZipFile()) + var archiveStream = new MemoryStream(); + using (var zipWrapper = new SharpZipLibWrapper(archiveStream)) { // export the assessments foreach (ASSESSMENTS a in exportAssessments) @@ -688,7 +689,7 @@ public MemoryStream BulkExportAssessments(Guid[] guids, string fileExtension) int duplicateCounter = 1; // Handle collision cases where assessments have the same name, zip entries must be different. - while (archive.ContainsEntry(exportFile.FileName)) + while (zipWrapper.ContainsEntry(exportFile.FileName)) { if (duplicateCounter == 1) { @@ -702,10 +703,12 @@ public MemoryStream BulkExportAssessments(Guid[] guids, string fileExtension) duplicateCounter++; } - archive.AddEntry(exportFile.FileName, exportFile.FileContents); - } - archive.Save(archiveStream); + zipWrapper.AddEntry(exportFile.FileName, exportFile.FileContents); + + zipWrapper.Save(); + zipWrapper.CloseStream(); + } } archiveStream.Seek(0, SeekOrigin.Begin); diff --git a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Import/ImportManager.cs b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Import/ImportManager.cs index e7af4bddc4..ea50d69f63 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Import/ImportManager.cs +++ b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/Import/ImportManager.cs @@ -4,15 +4,13 @@ // // //////////////////////////////// -using CSETWebCore.Business.Diagram; -//using CSETWebCore.Business.ImportAssessment.Models.Version_10_1; using CSETWebCore.Business.AssessmentIO.Models.AutoGenerated; +using CSETWebCore.Business.Diagram; using CSETWebCore.DataLayer.Model; using CSETWebCore.Helpers; using CSETWebCore.Interfaces.Helpers; using CSETWebCore.Model.AssessmentIO; using CSETWebCore.Model.Diagram; -using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using System; @@ -24,9 +22,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; -using Ionic.Zip; -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; +using ICSharpCode.SharpZipLib.Zip; namespace CSETWebCore.Business.AssessmentIO.Import { @@ -63,18 +59,23 @@ public async Task ProcessCSETAssessmentImport(byte[] zipFileFromDatabase, int? c using (Stream fs = new MemoryStream(zipFileFromDatabase)) { MemoryStream ms = new MemoryStream(); - ZipFile zip = ZipFile.Read(fs); - ZipEntry e = zip["model.json"]; - if (password == "" || password == null) + var x = new ZipFile(fs); + ZipEntry e = x.GetEntry("model.json"); + + + + if (!String.IsNullOrEmpty(password)) { - e.Extract(ms); + x.Password = password; } - else + + using (var zipStream = x.GetInputStream(e)) { - e.ExtractWithPassword(ms, password); + zipStream.CopyTo(ms); } + ms.Position = 0; StreamReader sr = new StreamReader(ms); string jsonObject = sr.ReadToEnd(); @@ -238,7 +239,11 @@ public async Task ProcessCSETAssessmentImport(byte[] zipFileFromDatabase, int? c //NOTE THAT THIS ENTRY WILL ONLY COME FROM A OLD .cset file //IMPORT //ZipArchiveEntry importLegacyDiagram = zip.GetEntry("Diagram.csetd"); - ZipEntry importLegacyDiagram = zip["Diagram.csetd"]; + //ZipEntry importLegacyDiagram = zip["Diagram.csetd"]; + + ZipEntry importLegacyDiagram = x.GetEntry("Diagram.csetd"); + + if (importLegacyDiagram != null) { //StreamReader ldr = new StreamReader(importLegacyDiagram.Open()); @@ -266,56 +271,28 @@ public async Task BulkImportAssessments(Stream assessmentsZipArchive, bool overw { using (assessmentsZipArchive) { - ZipFile zip = ZipFile.Read(assessmentsZipArchive); + // ZipFile zip = ZipFile.Read(assessmentsZipArchive); + + var zip = new ZipFile(assessmentsZipArchive); foreach (ZipEntry entry in zip) { using (MemoryStream stream = new MemoryStream()) { - entry.Extract(stream); + //entry.Extract(stream); + + using (var zipStream = zip.GetInputStream(entry)) + { + zipStream.CopyTo(stream); + } + + await ProcessCSETAssessmentImport(stream.ToArray(), null, null, _context, overwriteAssessment: overwriteAssessments); } } } } - /// - /// - /// - /// - /// - /*private void SaveFileToDB(ZipArchiveEntry entry, DOCUMENT_FILE doc) - { - var stream = entry.Open(); - - // determine the content type - var provider = new FileExtensionContentTypeProvider(); - string contentType; - if (!provider.TryGetContentType(entry.FullName, out contentType)) - { - contentType = "application/octet-stream"; - } - - - string fileHash; - byte[] bytes; - using (var ms = new MemoryStream()) - { - stream.CopyTo(ms); - bytes = ms.ToArray(); - } - // Hash the file so that we can determine if it is already attached to another question - using (var md5 = MD5.Create()) - { - var hash = md5.ComputeHash(bytes); - fileHash = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); - } - doc.UpdatedTimestamp = DateTime.Now; - doc.ContentType = contentType; - doc.Name = entry.Name; - doc.Data = bytes; - }*/ - /// /// diff --git a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/ZipWrapper.cs b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/ZipWrapper.cs new file mode 100644 index 0000000000..0d2ad580f1 --- /dev/null +++ b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/AssessmentIO/ZipWrapper.cs @@ -0,0 +1,106 @@ +//////////////////////////////// +// +// Copyright 2024 Battelle Energy Alliance, LLC +// +// +//////////////////////////////// +using ICSharpCode.SharpZipLib.Zip; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + + +namespace CSETWebCore.Business.AssessmentIO +{ + public class SharpZipLibWrapper : IDisposable + { + public readonly ZipOutputStream _zipStream; + private readonly HashSet _addedEntries; + + + public SharpZipLibWrapper(Stream outputStream) + { + _zipStream = new ZipOutputStream(outputStream); + _addedEntries = new HashSet(); + } + + + public string Password + { + get => _zipStream.Password; + set => _zipStream.Password = value; + } + + + public void AddEntry(string entryName, string content) + { + if (_addedEntries.Contains(entryName)) + { + throw new InvalidOperationException($"Entry '{entryName}' already exists in the ZIP archive."); + } + + var entry = new ZipEntry(entryName) + { + DateTime = DateTime.Now + }; + + _zipStream.PutNextEntry(entry); + byte[] buffer = Encoding.UTF8.GetBytes(content); + _zipStream.Write(buffer, 0, buffer.Length); + _zipStream.CloseEntry(); + + _addedEntries.Add(entryName); + } + + + public void AddEntry(string entryName, Stream contentStream) + { + if (_addedEntries.Contains(entryName)) + { + throw new InvalidOperationException($"Entry '{entryName}' already exists in the ZIP archive."); + } + + var entry = new ZipEntry(entryName) + { + DateTime = DateTime.Now + }; + + _zipStream.PutNextEntry(entry); + contentStream.CopyTo(_zipStream); + _zipStream.CloseEntry(); + + _addedEntries.Add(entryName); + } + + + /// + /// + /// + public bool ContainsEntry(string entryName) + { + return _addedEntries.Contains(entryName); + } + + + /// + /// + /// + public void Save() + { + _zipStream.Finish(); + } + + + public void CloseStream() + { + _zipStream.IsStreamOwner = false; // Avoid closing the underlying stream + _zipStream.Close(); + } + + public void Dispose() + { + _zipStream.Close(); + } + } +} diff --git a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/CSETWebCore.Business.csproj b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/CSETWebCore.Business.csproj index 135115b2f8..fc7983c8da 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/CSETWebCore.Business.csproj +++ b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/CSETWebCore.Business.csproj @@ -118,7 +118,6 @@ - @@ -131,6 +130,7 @@ + diff --git a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Demographic/DemographicIO/Export/ExportDemographicsManager.cs b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Demographic/DemographicIO/Export/ExportDemographicsManager.cs index 02238f9d55..f305404c15 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Demographic/DemographicIO/Export/ExportDemographicsManager.cs +++ b/CSETWebApi/CSETWeb_Api/CSETWebCore.Business/Demographic/DemographicIO/Export/ExportDemographicsManager.cs @@ -10,7 +10,7 @@ using System.IO; using System.Linq; using System.Text; -using Ionic.Zip; +using ICSharpCode.SharpZipLib.Zip; using CSETWebCore.Business.Demographic.DemographicIO.Models; using CSETWebCore.Business.Demographic.DemographicIO; @@ -99,7 +99,7 @@ private Stream ArchiveStream(int assessmentId) var archiveStream = new MemoryStream(); var model = CopyForExport(assessmentId); - using (var archive = new ZipFile()) + using (var archive = new ZipOutputStream(archiveStream)) { var json = JsonConvert.SerializeObject(model, Formatting.Indented); @@ -134,8 +134,5 @@ public DemographicsExportFile ExportDemographics(int assessmentId, string fileEx return new DemographicsExportFile(fileName, assessmentFileContents); } - - - } } diff --git a/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/AssessmentImportController.cs b/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/AssessmentImportController.cs index be1207b71c..1d462db7dc 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/AssessmentImportController.cs +++ b/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/AssessmentImportController.cs @@ -14,7 +14,8 @@ using System; using System.IO; using System.Threading.Tasks; -using Ionic.Zip; +using ICSharpCode.SharpZipLib.Zip; + namespace CSETWebCore.Api.Controllers { @@ -110,11 +111,12 @@ public async Task ImportAssessment([FromHeader] string pwd) using (Stream fs = new MemoryStream(bytes)) { MemoryStream ms = new MemoryStream(); - ZipFile zip = ZipFile.Read(fs); + + var zip = new ZipFile(fs); foreach (ZipEntry entry in zip) { - if (entry.FileName.Contains(".hint")) + if (entry.Name.Contains(".hint")) { hint = entry; } @@ -128,14 +130,14 @@ public async Task ImportAssessment([FromHeader] string pwd) { var returnMessage = ""; - if (e.Message == "Exception of type 'Ionic.Zip.BadPasswordException' was thrown.") + if (e.Message == "No password available for encrypted stream") { - returnMessage = (hint == null) ? "Bad Password Exception" : "Bad Password Exception - " + hint.FileName; + returnMessage = (hint == null) ? "Bad Password Exception" : "Bad Password Exception - " + hint.Name; return StatusCode(423, returnMessage); } else if (e.Message == "The password did not match.") { - returnMessage = (hint == null) ? "Invalid Password" : "Invalid Password - " + hint.FileName; + returnMessage = (hint == null) ? "Invalid Password" : "Invalid Password - " + hint.Name; return StatusCode(406, returnMessage); } else if (e.Message == "Custom module not found") diff --git a/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/DemographicsImportController.cs b/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/DemographicsImportController.cs index b04aa9c83d..f542fe4589 100644 --- a/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/DemographicsImportController.cs +++ b/CSETWebApi/CSETWeb_Api/CSETWeb_ApiCore/Controllers/DemographicsImportController.cs @@ -4,19 +4,14 @@ // // //////////////////////////////// -using CSETWebCore.Business.AssessmentIO.Import; +using CSETWebCore.Business.Demographic.Import; using CSETWebCore.DataLayer.Model; -using CSETWebCore.Helpers; using CSETWebCore.Interfaces.Helpers; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; using System; using System.IO; using System.Threading.Tasks; -using Ionic.Zip; -using CSETWebCore.Business.Demographic.Import; -using Newtonsoft.Json; namespace CSETWebCore.Api.Controllers { diff --git a/CSETWebApi/CSETWeb_Api/DuplicateAssessments/MockTokenManager.cs b/CSETWebApi/CSETWeb_Api/DuplicateAssessments/MockTokenManager.cs index 11c8887d96..72657ac64d 100644 --- a/CSETWebApi/CSETWeb_Api/DuplicateAssessments/MockTokenManager.cs +++ b/CSETWebApi/CSETWeb_Api/DuplicateAssessments/MockTokenManager.cs @@ -608,5 +608,15 @@ public int UnixTime() TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1)); return (int)t.TotalSeconds; } + + public void SetEnterpriseToken(string tokenString) + { + throw new NotImplementedException(); + } + + public string GetEnterpriseToken() + { + throw new NotImplementedException(); + } } } diff --git a/CSETWebNg/src/app/services/import-assessment.service.ts b/CSETWebNg/src/app/services/import-assessment.service.ts index 01fc08f1fb..d32ad5a1ce 100644 --- a/CSETWebNg/src/app/services/import-assessment.service.ts +++ b/CSETWebNg/src/app/services/import-assessment.service.ts @@ -89,7 +89,6 @@ export class ImportAssessmentService { // Make sure our assessment hints are empty ahead of time this.hintMap.clear(); - // send the http-request and subscribe for progress-updates this.http.request(req).subscribe(event => { if (event.type === HttpEventType.UploadProgress) { @@ -161,8 +160,4 @@ export class ImportAssessmentService { return this.http.post(this.configSvc.apiUrl + 'assessment/import', assessmentModel, headers); } - legacyAssessmentImport(importFilepath: string) { - return this.http.post(this.configSvc.apiUrl + 'assessment/legacy/import', importFilepath, headers); - } - }