From a1fb803d0fab134769c53e55960aff00aae4fb46 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sat, 11 May 2024 04:11:09 +0100 Subject: [PATCH] Add upload report controller. --- Aaru.Server.New/Aaru.Server.New.csproj | 3 + .../Controllers/UploadReportController.cs | 385 ++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 Aaru.Server.New/Controllers/UploadReportController.cs diff --git a/Aaru.Server.New/Aaru.Server.New.csproj b/Aaru.Server.New/Aaru.Server.New.csproj index 09bf102e..ba5aa666 100644 --- a/Aaru.Server.New/Aaru.Server.New.csproj +++ b/Aaru.Server.New/Aaru.Server.New.csproj @@ -15,11 +15,14 @@ + + + diff --git a/Aaru.Server.New/Controllers/UploadReportController.cs b/Aaru.Server.New/Controllers/UploadReportController.cs new file mode 100644 index 00000000..6fbae5ff --- /dev/null +++ b/Aaru.Server.New/Controllers/UploadReportController.cs @@ -0,0 +1,385 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : UploadReportController.cs +// Author(s) : Natalia Portillo +// +// Component : Aaru Server. +// +// --[ Description ] ---------------------------------------------------------- +// +// Handles report uploads. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2024 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics; +using System.Net; +using System.Text; +using System.Xml.Serialization; +using Aaru.CommonTypes.Metadata; +using Aaru.Server.Database.Models; +using Cinchoo.PGP; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MimeKit; +using Newtonsoft.Json; +using DbContext = Aaru.Server.Database.DbContext; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; + +namespace Aaru.Server.New.Controllers; + +public sealed class UploadReportController : ControllerBase +{ + readonly DbContext _ctx; + readonly IWebHostEnvironment _environment; + + public UploadReportController(IWebHostEnvironment environment, DbContext ctx) + { + _environment = environment; + _ctx = ctx; + } + + /// Receives a report from Aaru.Core, verifies it's in the correct format and stores it on the server + /// HTTP response + [Route("api/uploadreport")] + [HttpPost] + public async Task UploadReport() + { + var response = new ContentResult + { + StatusCode = (int)HttpStatusCode.OK, + ContentType = "text/plain" + }; + + try + { + var newReport = new DeviceReport(); + HttpRequest request = HttpContext.Request; + + var xs = new XmlSerializer(newReport.GetType()); + + newReport = + (DeviceReport)xs.Deserialize(new StringReader(await new StreamReader(request.Body).ReadToEndAsync())); + + if(newReport == null) + { + response.Content = "notstats"; + + return response; + } + + var reportV2 = new DeviceReportV2(newReport); + var jsonSw = new StringWriter(); + + await jsonSw.WriteAsync(JsonConvert.SerializeObject(reportV2, + Formatting.Indented, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + })); + + var reportV2String = jsonSw.ToString(); + jsonSw.Close(); + + var newUploadedReport = new UploadedReport(reportV2); + + // Ensure CHS and CurrentCHS are not duplicates + if(newUploadedReport.ATA?.ReadCapabilities is { CHS: not null, CurrentCHS: not null } && + newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Cylinders && + newUploadedReport.ATA.ReadCapabilities.CHS.Heads == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads && + newUploadedReport.ATA.ReadCapabilities.CHS.Sectors == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors) + newUploadedReport.ATA.ReadCapabilities.CHS = newUploadedReport.ATA.ReadCapabilities.CurrentCHS; + + // Check if the CHS or CurrentCHS of this report already exist in the database + if(newUploadedReport.ATA?.ReadCapabilities?.CHS != null) + { + Chs? existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => + c.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders && + c.Heads == + newUploadedReport.ATA.ReadCapabilities.CHS.Heads && + c.Sectors == + newUploadedReport.ATA.ReadCapabilities.CHS.Sectors); + + if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CHS = existingChs; + } + + if(newUploadedReport.ATA?.ReadCapabilities?.CurrentCHS != null) + { + Chs? existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => + c.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS + .Cylinders && + c.Heads == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads && + c.Sectors == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors); + + if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CurrentCHS = existingChs; + } + + if(newUploadedReport.ATA?.RemovableMedias != null) + { + foreach(TestedMedia media in newUploadedReport.ATA.RemovableMedias) + { + Chs? existingChs; + + if(media.CHS != null && + media.CurrentCHS != null && + media.CHS.Cylinders == media.CurrentCHS.Cylinders && + media.CHS.Heads == media.CurrentCHS.Heads && + media.CHS.Sectors == media.CurrentCHS.Sectors) + media.CHS = media.CurrentCHS; + + if(media.CHS != null) + { + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => c.Cylinders == media.CHS.Cylinders && + c.Heads == media.CHS.Heads && + c.Sectors == media.CHS.Sectors); + + if(existingChs != null) media.CHS = existingChs; + } + + if(media.CHS == null) continue; + + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => media.CurrentCHS != null && + c.Cylinders == media.CurrentCHS.Cylinders && + c.Heads == media.CurrentCHS.Heads && + c.Sectors == media.CurrentCHS.Sectors); + + if(existingChs != null) media.CurrentCHS = existingChs; + } + } + + _ctx.Reports.Add(newUploadedReport); + await _ctx.SaveChangesAsync(); + + var pgpIn = new MemoryStream(Encoding.UTF8.GetBytes(reportV2String)); + var pgpOut = new MemoryStream(); + var pgp = new ChoPGPEncryptDecrypt(); + + await pgp.EncryptAsync(pgpIn, + pgpOut, + Path.Combine(_environment.ContentRootPath ?? throw new InvalidOperationException(), + "public.asc")); + + pgpOut.Position = 0; + reportV2String = Encoding.UTF8.GetString(pgpOut.ToArray()); + + var message = new MimeMessage + { + Subject = "New device report (old version)", + Body = new TextPart("plain") + { + Text = reportV2String + } + }; + + message.From.Add(new MailboxAddress("Aaru Server", "aaru@claunia.com")); + message.To.Add(new MailboxAddress("Natalia Portillo", "claunia@claunia.com")); + + using(var client = new SmtpClient()) + { + await client.ConnectAsync("mail.claunia.com", 25, false); + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + + response.Content = "ok"; + + return response; + } + + // ReSharper disable once RedundantCatchClause + catch + { +#if DEBUG + if(Debugger.IsAttached) throw; +#endif + response.Content = "error"; + + return response; + } + } + + /// Receives a report from Aaru.Core, verifies it's in the correct format and stores it on the server + /// HTTP response + [Route("api/uploadreportv2")] + [HttpPost] + public async Task UploadReportV2() + { + var response = new ContentResult + { + StatusCode = (int)HttpStatusCode.OK, + ContentType = "text/plain" + }; + + try + { + HttpRequest request = HttpContext.Request; + + var sr = new StreamReader(request.Body); + string reportJson = await sr.ReadToEndAsync(); + DeviceReportV2 newReport = JsonConvert.DeserializeObject(reportJson); + + if(newReport == null) + { + response.Content = "notstats"; + + return response; + } + + var newUploadedReport = new UploadedReport(newReport); + + // Ensure CHS and CurrentCHS are not duplicates + if(newUploadedReport.ATA?.ReadCapabilities is { CHS: not null, CurrentCHS: not null } && + newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Cylinders && + newUploadedReport.ATA.ReadCapabilities.CHS.Heads == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads && + newUploadedReport.ATA.ReadCapabilities.CHS.Sectors == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors) + newUploadedReport.ATA.ReadCapabilities.CHS = newUploadedReport.ATA.ReadCapabilities.CurrentCHS; + + Chs? existingChs; + + // Check if the CHS or CurrentCHS of this report already exist in the database + if(newUploadedReport.ATA?.ReadCapabilities?.CHS != null) + { + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => + c.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CHS.Cylinders && + c.Heads == + newUploadedReport.ATA.ReadCapabilities.CHS.Heads && + c.Sectors == + newUploadedReport.ATA.ReadCapabilities.CHS.Sectors); + + if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CHS = existingChs; + } + + if(newUploadedReport.ATA?.ReadCapabilities?.CurrentCHS != null) + { + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => + c.Cylinders == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS + .Cylinders && + c.Heads == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Heads && + c.Sectors == + newUploadedReport.ATA.ReadCapabilities.CurrentCHS.Sectors); + + if(existingChs != null) newUploadedReport.ATA.ReadCapabilities.CurrentCHS = existingChs; + } + + if(newUploadedReport.ATA?.RemovableMedias != null) + { + foreach(TestedMedia media in newUploadedReport.ATA.RemovableMedias) + { + if(media.CHS != null && + media.CurrentCHS != null && + media.CHS.Cylinders == media.CurrentCHS.Cylinders && + media.CHS.Heads == media.CurrentCHS.Heads && + media.CHS.Sectors == media.CurrentCHS.Sectors) + media.CHS = media.CurrentCHS; + + if(media.CHS != null) + { + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => c.Cylinders == media.CHS.Cylinders && + c.Heads == media.CHS.Heads && + c.Sectors == media.CHS.Sectors); + + if(existingChs != null) media.CHS = existingChs; + } + + if(media.CHS == null) continue; + + + existingChs = + await _ctx.Chs.FirstOrDefaultAsync(c => media.CurrentCHS != null && + c.Cylinders == media.CurrentCHS.Cylinders && + c.Heads == media.CurrentCHS.Heads && + c.Sectors == media.CurrentCHS.Sectors); + + if(existingChs != null) media.CurrentCHS = existingChs; + } + } + + _ctx.Reports.Add(newUploadedReport); + await _ctx.SaveChangesAsync(); + + var pgpIn = new MemoryStream(Encoding.UTF8.GetBytes(reportJson)); + var pgpOut = new MemoryStream(); + var pgp = new ChoPGPEncryptDecrypt(); + + await pgp.EncryptAsync(pgpIn, + pgpOut, + Path.Combine(_environment.ContentRootPath ?? throw new InvalidOperationException(), + "public.asc")); + + pgpOut.Position = 0; + reportJson = Encoding.UTF8.GetString(pgpOut.ToArray()); + + var message = new MimeMessage + { + Subject = "New device report", + Body = new TextPart("plain") + { + Text = reportJson + } + }; + + message.From.Add(new MailboxAddress("Aaru Server", "aaru@claunia.com")); + message.To.Add(new MailboxAddress("Natalia Portillo", "claunia@claunia.com")); + + using(var client = new SmtpClient()) + { + await client.ConnectAsync("mail.claunia.com", 25, false); + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + + response.Content = "ok"; + + return response; + } + + // ReSharper disable once RedundantCatchClause + catch + { +#if DEBUG + if(Debugger.IsAttached) throw; +#endif + response.Content = "error"; + + return response; + } + } +} \ No newline at end of file