diff --git a/bindings/netstandard/ElectionGuard/ElectionGuard.ElectionSetup/Guardian/GuardianStorageExtensions.cs b/bindings/netstandard/ElectionGuard/ElectionGuard.ElectionSetup/Guardian/GuardianStorageExtensions.cs index c0ef9561f..29929a179 100644 --- a/bindings/netstandard/ElectionGuard/ElectionGuard.ElectionSetup/Guardian/GuardianStorageExtensions.cs +++ b/bindings/netstandard/ElectionGuard/ElectionGuard.ElectionSetup/Guardian/GuardianStorageExtensions.cs @@ -1,4 +1,4 @@ -using ElectionGuard.Encryption.Utils.Converters; +using ElectionGuard.Encryption.Utils.Converters; using ElectionGuard.UI.Lib.Models; using ElectionGuard.UI.Lib.Services; using Newtonsoft.Json; @@ -79,6 +79,7 @@ public static void Save(this Guardian self, string keyCeremonyId) var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var filePath = Path.Combine(basePath, PrivateKeyFolder, keyCeremonyId); - storage.ToFile(filePath, filename, dataJson); + storage.UpdatePath(filePath); + storage.ToFile(filename, dataJson); } } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ConstantsRecord.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ConstantsRecord.cs index f64e6d4b3..2f4999609 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ConstantsRecord.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ConstantsRecord.cs @@ -18,4 +18,6 @@ public ConstantsRecord() : base(nameof(ConstantsRecord)) { } + public override string ToString() => ConstantsData ?? string.Empty; + public static implicit operator string(ConstantsRecord record) => record.ToString(); } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ContextRecord.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ContextRecord.cs index 16b094736..7092b6b07 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ContextRecord.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ContextRecord.cs @@ -12,4 +12,6 @@ public ContextRecord() : base(nameof(ContextRecord)) { } + public override string ToString() => ContextData ?? string.Empty; + public static implicit operator string(ContextRecord record) => record.ToString(); } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ManifestRecord.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ManifestRecord.cs index 60540c235..60c2ec1c1 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ManifestRecord.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Models/ManifestRecord.cs @@ -16,4 +16,7 @@ public ManifestRecord(string electionId, string manifestData) : base(nameof(Mani public ManifestRecord() : base(nameof(ManifestRecord)) { } + + public override string ToString() => ManifestData ?? string.Empty; + public static implicit operator string(ManifestRecord record) => record.ToString(); } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Database/ElectionService.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Database/ElectionService.cs index ed94b3f8a..cb56acbda 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Database/ElectionService.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Database/ElectionService.cs @@ -42,7 +42,7 @@ public async Task ElectionNameExists(string electionName) /// /// election id to use /// date to save - virtual public async Task UpdateExportDateAsync(string electionId, DateTime date) + virtual public async Task UpdateEncryptionExportDateAsync(string electionId, DateTime date) { var filterBuilder = Builders.Filter; var filter = filterBuilder.And(filterBuilder.Eq(Constants.ElectionId, electionId)); @@ -52,5 +52,4 @@ virtual public async Task UpdateExportDateAsync(string electionId, DateTime date await UpdateAsync(filter, update); } - } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/DriveService.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/DriveService.cs index 824c50a91..405cad1e2 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/DriveService.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/DriveService.cs @@ -1,30 +1,56 @@ -namespace ElectionGuard.UI.Lib.Services; +using System.IO.Compression; +using ElectionGuard.UI.Lib.Models; + +namespace ElectionGuard.UI.Lib.Services; -/// -/// Class to read and write file to the hard drive -/// public class DriveService : IStorageService { - /// - /// Read in the contents of a file - /// - /// filename including path - /// the string data from the file - public string FromFile(string filename) + private string _rootDirectoryPath; + + public DriveService(string rootDirectoryPath) + { + _rootDirectoryPath = rootDirectoryPath; + } + + public DriveService() : this(Path.GetTempPath()) + { + } + + public void ToFile(string fileName, string content) + { + var filePath = Path.Combine(_rootDirectoryPath, fileName); + File.WriteAllText(filePath, content); + } + + public void ToFiles(List fileContents) { - return File.ReadAllText(filename); + Parallel.ForEach(fileContents, fileContent => + { + ToFile(fileContent.FileName, fileContent.Contents); + }); } - /// - /// Write a string to a file on a drive - /// - /// folder where the file will be created - /// name of the file - /// data to save into file - public void ToFile(string path, string filename, string data) + public string FromFile(string fileName) { - Directory.CreateDirectory(path); - var name = Path.Combine(path, filename); - File.WriteAllText(name, data); + if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName)); + + var filePath = Path.Combine(_rootDirectoryPath, fileName); + + if (!File.Exists(filePath)) throw new FileNotFoundException(nameof(filePath)); + + return File.ReadAllText(filePath); + } + + public void UpdatePath(string path) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + + if (! Directory.Exists(path)) + { + // create directory + Directory.CreateDirectory(path); + + _rootDirectoryPath = path; + } } } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/ZipService.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/ZipService.cs index 1840ef62e..443cc4afe 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/ZipService.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/FileSystem/ZipService.cs @@ -2,42 +2,58 @@ using ElectionGuard.UI.Lib.Models; namespace ElectionGuard.UI.Lib.Services; - -/// -/// Service to allow simple creation and managing of zip files -/// -public static class ZipService +public class ZipStorageService : IStorageService { - /// - /// Creates a zip file if it does not exist - /// - /// file name and path where to make the zip file - private static void CreateZip(string zipFileName) + private string _zipFile; + + private ZipArchiveMode ZipMode => File.Exists(_zipFile) ? ZipArchiveMode.Update : ZipArchiveMode.Create; + + public ZipStorageService(string zipFile) { - if (!File.Exists(zipFileName)) + UpdatePath(zipFile); + } + + public ZipStorageService() : this(Path.GetTempFileName()) { } + + public void ToFile(string fileName, string content) + { + if (string.IsNullOrEmpty(fileName)) { - // Create the empty zip file - ZipFile.CreateFromDirectory(".", zipFileName, CompressionLevel.Optimal, false); + throw new ArgumentNullException(nameof(fileName)); } + + using var zipArchive = ZipFile.Open(_zipFile, ZipMode); + + var entry = zipArchive.CreateEntry(fileName); + using var stream = entry.Open(); + using var writer = new StreamWriter(stream); + + writer.Write(content); } - /// - /// Adds a file to the given zip file. If the zip file does not exist, it creates it first - /// - /// filename and path of the zip file to use / make - /// list of file data to add to the zip file - public static void AddFiles(string zipFileName, List fileList) + public void ToFiles(List fileContents) { - CreateZip(zipFileName); + using var zipArchive = ZipFile.Open(_zipFile, ZipMode); + _ = Parallel.ForEach(fileContents, fileContent => + { + var entry = zipArchive.CreateEntry(fileContent.FileName); + using var stream = entry.Open(); + using var writer = new StreamWriter(stream); + writer.Write(fileContent.Contents); + }); + } - using var archive = ZipFile.Open(zipFileName, ZipArchiveMode.Update); + public string FromFile(string fileName) + { + using var zipArchive = ZipFile.OpenRead(_zipFile); + var entry = zipArchive.GetEntry(fileName) ?? throw new FileNotFoundException(nameof(fileName)); + using var stream = entry.Open(); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } - foreach (var file in fileList) - { - // Create a new entry for the file in the zip file - var entry = archive.CreateEntry(file.FileName.Replace('\\', '/'), CompressionLevel.Optimal); - using var writer = new StreamWriter(entry.Open()); - writer.Write(file.Contents); - } + public void UpdatePath(string zipFile) + { + _zipFile = zipFile; } } diff --git a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Interfaces/IStorageService.cs b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Interfaces/IStorageService.cs index 7db6e8ce4..7fdfbbdc7 100644 --- a/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Interfaces/IStorageService.cs +++ b/src/electionguard-ui/ElectionGuard.UI.Lib/Services/Interfaces/IStorageService.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using ElectionGuard.UI.Lib.Models; namespace ElectionGuard.UI.Lib.Services { @@ -17,14 +18,30 @@ public interface IStorageService /// folder where the file will be created /// name of the file /// data to save into file - public void ToFile(string path, string filename, string data); + public void ToFile(string filename, string content); + + /// + /// Write multiple files to a drive + /// + /// + public void ToFiles(List files); /// /// Read in the contents of a file /// /// filename including path /// the string data from the file + /// File does not exist + /// filename not provided public string FromFile(string filename); + + /// + /// Update the path for a file + /// + /// new path + /// Path is null + /// Path does not exist + public void UpdatePath(string path); } /// diff --git a/src/electionguard-ui/ElectionGuard.UI/ElectionGuard.UI.csproj b/src/electionguard-ui/ElectionGuard.UI/ElectionGuard.UI.csproj index 49f6d59a7..0ce20ad8f 100644 --- a/src/electionguard-ui/ElectionGuard.UI/ElectionGuard.UI.csproj +++ b/src/electionguard-ui/ElectionGuard.UI/ElectionGuard.UI.csproj @@ -183,9 +183,6 @@ True AppResources.resx - - EncryptionExportPage.xaml - CreateElectionAdminPage.xaml diff --git a/src/electionguard-ui/ElectionGuard.UI/Helpers/StorageUtils.cs b/src/electionguard-ui/ElectionGuard.UI/Helpers/StorageUtils.cs new file mode 100644 index 000000000..d38d52afd --- /dev/null +++ b/src/electionguard-ui/ElectionGuard.UI/Helpers/StorageUtils.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace ElectionGuard.UI.Helpers +{ + public static class StorageUtils + { +#if WINDOWS + [Flags] + public enum FileSystemFeature : uint + { + /// + /// The file system preserves the case of file names when it places a name on disk. + /// + CasePreservedNames = 2, + + /// + /// The file system supports case-sensitive file names. + /// + CaseSensitiveSearch = 1, + + /// + /// The specified volume is a direct access (DAX) volume. This flag was introduced in Windows 10, version 1607. + /// + DaxVolume = 0x20000000, + + /// + /// The file system supports file-based compression. + /// + FileCompression = 0x10, + + /// + /// The file system supports named streams. + /// + NamedStreams = 0x40000, + + /// + /// The file system preserves and enforces access control lists (ACL). + /// + PersistentACLS = 8, + + /// + /// The specified volume is read-only. + /// + ReadOnlyVolume = 0x80000, + + /// + /// The volume supports a single sequential write. + /// + SequentialWriteOnce = 0x100000, + + /// + /// The file system supports the Encrypted File System (EFS). + /// + SupportsEncryption = 0x20000, + + /// + /// The specified volume supports extended attributes. An extended attribute is a piece of + /// application-specific metadata that an application can associate with a file and is not part + /// of the file's data. + /// + SupportsExtendedAttributes = 0x00800000, + + /// + /// The specified volume supports hard links. For more information, see Hard Links and Junctions. + /// + SupportsHardLinks = 0x00400000, + + /// + /// The file system supports object identifiers. + /// + SupportsObjectIDs = 0x10000, + + /// + /// The file system supports open by FileID. For more information, see FILE_ID_BOTH_DIR_INFO. + /// + SupportsOpenByFileId = 0x01000000, + + /// + /// The file system supports re-parse points. + /// + SupportsReparsePoints = 0x80, + + /// + /// The file system supports sparse files. + /// + SupportsSparseFiles = 0x40, + + /// + /// The volume supports transactions. + /// + SupportsTransactions = 0x200000, + + /// + /// The specified volume supports update sequence number (USN) journals. For more information, + /// see Change Journal Records. + /// + SupportsUsnJournal = 0x02000000, + + /// + /// The file system supports Unicode in file names as they appear on disk. + /// + UnicodeOnDisk = 4, + + /// + /// The specified volume is a compressed volume, for example, a DoubleSpace volume. + /// + VolumeIsCompressed = 0x8000, + + /// + /// The file system supports disk quotas. + /// + VolumeQuotas = 0x20 + } + + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool GetVolumeInformation( + string rootPathName, + StringBuilder volumeNameBuffer, + int volumeNameSize, + out uint volumeSerialNumber, + out uint maximumComponentLength, + out FileSystemFeature fileSystemFlags, + StringBuilder fileSystemNameBuffer, + int nFileSystemNameSize); +#endif + public static bool GetVolumeInformation(string rootPathName, out uint volumeSerialNumber) + { + const int NAME_SIZE = 32; + volumeSerialNumber = StorageUtils.UnknownSerial; + StringBuilder f = new(NAME_SIZE); + StringBuilder f2 = new(NAME_SIZE); + +#if WINDOWS + return StorageUtils.GetVolumeInformation(rootPathName, f, NAME_SIZE, out volumeSerialNumber, out _, out _, f2, NAME_SIZE); +#endif + + // TODO: implement for non-windows. + return false; + } + + public const uint UnknownSerial = 0U; + + } +} diff --git a/src/electionguard-ui/ElectionGuard.UI/Helpers/Constants.cs b/src/electionguard-ui/ElectionGuard.UI/Helpers/UISettings.cs similarity index 100% rename from src/electionguard-ui/ElectionGuard.UI/Helpers/Constants.cs rename to src/electionguard-ui/ElectionGuard.UI/Helpers/UISettings.cs diff --git a/src/electionguard-ui/ElectionGuard.UI/MauiProgram.cs b/src/electionguard-ui/ElectionGuard.UI/MauiProgram.cs index b3290e192..bd20ad14e 100644 --- a/src/electionguard-ui/ElectionGuard.UI/MauiProgram.cs +++ b/src/electionguard-ui/ElectionGuard.UI/MauiProgram.cs @@ -62,6 +62,8 @@ public static MauiAppBuilder SetupServices(this MauiAppBuilder builder) builder.Services.AddSingleton(); // NavigationService has to be singleton because it stores the current page and vm builder.Services.AddSingleton(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); // setup database services builder.Services.AddTransient(); @@ -83,7 +85,6 @@ public static MauiAppBuilder SetupServices(this MauiAppBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddSingleton(); - builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -105,7 +106,6 @@ public static MauiAppBuilder SetupServices(this MauiAppBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); // popup pages builder.Services.AddTransient(); diff --git a/src/electionguard-ui/ElectionGuard.UI/Models/EncryptionPackage.cs b/src/electionguard-ui/ElectionGuard.UI/Models/EncryptionPackage.cs new file mode 100644 index 000000000..24ea5474e --- /dev/null +++ b/src/electionguard-ui/ElectionGuard.UI/Models/EncryptionPackage.cs @@ -0,0 +1,45 @@ +namespace ElectionGuard.UI.Models; + + +/// +/// Representative of all data needed to export an encryption Package +/// +public record EncryptionPackage +{ + public string Context { get; init; } + public string Constants { get; init; } + public string Manifest { get; init; } + + private const string MANIFEST_FILE = "manifest.json"; + private const string CONSTANTS_FILE = "constants.json"; + private const string CONTEXT_FILE = "context.json"; + + public EncryptionPackage(string context, string constants, string manifest) + { + Context = context; + Constants = constants; + Manifest = manifest; + } + + public EncryptionPackage(ContextRecord contextRecord, ConstantsRecord constantsRecord, ManifestRecord manifestRecord) + { + Context = contextRecord.ToString(); + Constants = constantsRecord.ToString(); + Manifest = manifestRecord.ToString(); + } + + + /// + /// Make a list of files for export into + /// + /// The election package + public static implicit operator List(EncryptionPackage package) + { + return new() + { + new(MANIFEST_FILE, package.Manifest), + new(CONTEXT_FILE, package.Context), + new(CONSTANTS_FILE, package.Constants), + }; + } +} diff --git a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.Designer.cs b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.Designer.cs index 9e58dbc43..8713568bc 100644 --- a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.Designer.cs +++ b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.Designer.cs @@ -483,6 +483,24 @@ internal static string ErrorManifest { } } + /// + /// Looks up a localized string similar to Please ensure the EGDrive removable storage is connected to the device. Please note, this will replace any encryption package currently on all EGDrive devices. + /// + internal static string ExportDriveWarning { + get { + return ResourceManager.GetString("ExportDriveWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export Encryption Package. + /// + internal static string ExportDriveWarningTitle { + get { + return ResourceManager.GetString("ExportDriveWarningTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Export Encryption. /// diff --git a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.es.resx b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.es.resx index 997447c93..039a454f8 100644 --- a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.es.resx +++ b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.es.resx @@ -492,4 +492,10 @@ ¡Revisa el desafío! + + Asegúrese de que el almacenamiento extraíble de EGDrive esté conectado al dispositivo. ¡Tenga en cuenta que esto reemplazará cualquier paquete de cifrado actualmente en todos los dispositivos EGDrive! + + + ¡Exportar paquete de cifrado! + \ No newline at end of file diff --git a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.resx b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.resx index 3fc8242a7..a62a30428 100644 --- a/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.resx +++ b/src/electionguard-ui/ElectionGuard.UI/Resx/AppResources.resx @@ -489,4 +489,10 @@ Review Challenge + + Please ensure the EGDrive removable storage is connected to the device. Please note, this will replace any encryption package currently on all EGDrive devices + + + Export Encryption Package + \ No newline at end of file diff --git a/src/electionguard-ui/ElectionGuard.UI/Services/NavigationService.cs b/src/electionguard-ui/ElectionGuard.UI/Services/NavigationService.cs index 426aac9ff..b06d48f61 100644 --- a/src/electionguard-ui/ElectionGuard.UI/Services/NavigationService.cs +++ b/src/electionguard-ui/ElectionGuard.UI/Services/NavigationService.cs @@ -26,7 +26,6 @@ public class NavigationService : INavigationService new PageType(typeof(BallotUploadViewModel), typeof(BallotUploadPage), false), new PageType(typeof(CreateTallyViewModel), typeof(CreateTallyPage), false), new PageType(typeof(TallyProcessViewModel), typeof(TallyProcessPage), false), - new PageType(typeof(EncryptionPackageExportViewModel), typeof(EncryptionExportPage), false), }; private Type _currentPage = typeof(LoginPage); diff --git a/src/electionguard-ui/ElectionGuard.UI/ViewModels/BallotUploadViewModel.cs b/src/electionguard-ui/ElectionGuard.UI/ViewModels/BallotUploadViewModel.cs index 23f3ce65c..f883174e7 100644 --- a/src/electionguard-ui/ElectionGuard.UI/ViewModels/BallotUploadViewModel.cs +++ b/src/electionguard-ui/ElectionGuard.UI/ViewModels/BallotUploadViewModel.cs @@ -1,12 +1,8 @@ -using System.Diagnostics; -using System.Reflection.Metadata.Ecma335; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; +using System.Text; using System.Text.Json; using CommunityToolkit.Maui.Storage; using CommunityToolkit.Mvvm.Input; - +using ElectionGuard.UI.Helpers; namespace ElectionGuard.UI.ViewModels; [QueryProperty(ElectionIdParam, nameof(ElectionId))] @@ -287,127 +283,6 @@ public BallotUploadViewModel(IServiceProvider serviceProvider, BallotUploadServi _timer.Start(); } -#if WINDOWS - [Flags] - public enum FileSystemFeature : uint - { - /// - /// The file system preserves the case of file names when it places a name on disk. - /// - CasePreservedNames = 2, - - /// - /// The file system supports case-sensitive file names. - /// - CaseSensitiveSearch = 1, - - /// - /// The specified volume is a direct access (DAX) volume. This flag was introduced in Windows 10, version 1607. - /// - DaxVolume = 0x20000000, - - /// - /// The file system supports file-based compression. - /// - FileCompression = 0x10, - - /// - /// The file system supports named streams. - /// - NamedStreams = 0x40000, - - /// - /// The file system preserves and enforces access control lists (ACL). - /// - PersistentACLS = 8, - - /// - /// The specified volume is read-only. - /// - ReadOnlyVolume = 0x80000, - - /// - /// The volume supports a single sequential write. - /// - SequentialWriteOnce = 0x100000, - - /// - /// The file system supports the Encrypted File System (EFS). - /// - SupportsEncryption = 0x20000, - - /// - /// The specified volume supports extended attributes. An extended attribute is a piece of - /// application-specific metadata that an application can associate with a file and is not part - /// of the file's data. - /// - SupportsExtendedAttributes = 0x00800000, - - /// - /// The specified volume supports hard links. For more information, see Hard Links and Junctions. - /// - SupportsHardLinks = 0x00400000, - - /// - /// The file system supports object identifiers. - /// - SupportsObjectIDs = 0x10000, - - /// - /// The file system supports open by FileID. For more information, see FILE_ID_BOTH_DIR_INFO. - /// - SupportsOpenByFileId = 0x01000000, - - /// - /// The file system supports re-parse points. - /// - SupportsReparsePoints = 0x80, - - /// - /// The file system supports sparse files. - /// - SupportsSparseFiles = 0x40, - - /// - /// The volume supports transactions. - /// - SupportsTransactions = 0x200000, - - /// - /// The specified volume supports update sequence number (USN) journals. For more information, - /// see Change Journal Records. - /// - SupportsUsnJournal = 0x02000000, - - /// - /// The file system supports Unicode in file names as they appear on disk. - /// - UnicodeOnDisk = 4, - - /// - /// The specified volume is a compressed volume, for example, a DoubleSpace volume. - /// - VolumeIsCompressed = 0x8000, - - /// - /// The file system supports disk quotas. - /// - VolumeQuotas = 0x20 - } - - [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public extern static bool GetVolumeInformation( - string rootPathName, - StringBuilder volumeNameBuffer, - int volumeNameSize, - out uint volumeSerialNumber, - out uint maximumComponentLength, - out FileSystemFeature fileSystemFlags, - StringBuilder fileSystemNameBuffer, - int nFileSystemNameSize); -#endif - private void _timer_Tick(object sender, EventArgs e) { if (_importing) @@ -425,11 +300,8 @@ private void _timer_Tick(object sender, EventArgs e) if (drive.DriveType == DriveType.Removable && drive.VolumeLabel.ToLower() == "egdrive") { _serialNumber = 0; -#if WINDOWS - StringBuilder f = new(32); - StringBuilder f2 = new(32); - GetVolumeInformation(drive.Name, f, 32, out _serialNumber, out _, out _, f2, 32); -#endif + _ = StorageUtils.GetVolumeInformation(drive.Name, out _serialNumber); + if (_serialNumber == _lastDrive) { _importing = false; diff --git a/src/electionguard-ui/ElectionGuard.UI/ViewModels/CreateElectionViewModel.cs b/src/electionguard-ui/ElectionGuard.UI/ViewModels/CreateElectionViewModel.cs index 614e9ce27..2f7323359 100644 --- a/src/electionguard-ui/ElectionGuard.UI/ViewModels/CreateElectionViewModel.cs +++ b/src/electionguard-ui/ElectionGuard.UI/ViewModels/CreateElectionViewModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; +using ElectionGuard.UI.Models; namespace ElectionGuard.UI.ViewModels; @@ -10,15 +11,23 @@ public partial class CreateElectionViewModel : BaseViewModel private readonly ManifestService _manifestService; private readonly ContextService _contextService; private readonly ConstantsService _constantsService; + private readonly IStorageService _storageService; private const string PageName = "CreateElection"; - public CreateElectionViewModel(IServiceProvider serviceProvider, KeyCeremonyService keyCeremonyService, ElectionService electionService, ManifestService manifestService, ContextService contextService, ConstantsService constantsService) : base(PageName, serviceProvider) + public CreateElectionViewModel(IServiceProvider serviceProvider, + KeyCeremonyService keyCeremonyService, + ElectionService electionService, + ManifestService manifestService, + ContextService contextService, + ConstantsService constantsService, + ZipStorageService storageService) : base(PageName, serviceProvider) { _keyCeremonyService = keyCeremonyService; _electionService = electionService; _manifestService = manifestService; _contextService = contextService; _constantsService = constantsService; + _storageService = storageService; } public override async Task OnAppearing() @@ -111,14 +120,9 @@ private async Task CreateElection() // create the export file for each election // need to add the path from the manifest var zipPath = file.FullPath.ToLower().Replace(".json", ".zip"); - var files = new List - { - new("constants.json", constantsRecord.ConstantsData), - new("context.json", contextRecord.ContextData), - new("manifest.json", manifestRecord.ManifestData) - }; - - ZipService.AddFiles(zipPath, files); + var encryptionPackage = new EncryptionPackage(contextRecord, constantsRecord, manifestRecord); + _storageService.UpdatePath(zipPath); + _storageService.ToFiles(encryptionPackage); } diff --git a/src/electionguard-ui/ElectionGuard.UI/ViewModels/ElectionViewModel.cs b/src/electionguard-ui/ElectionGuard.UI/ViewModels/ElectionViewModel.cs index 9e93e0fbb..26fa11de7 100644 --- a/src/electionguard-ui/ElectionGuard.UI/ViewModels/ElectionViewModel.cs +++ b/src/electionguard-ui/ElectionGuard.UI/ViewModels/ElectionViewModel.cs @@ -1,25 +1,43 @@ -using System.Linq; -using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; - +using ElectionGuard.UI.Models; + namespace ElectionGuard.UI.ViewModels; [QueryProperty(CurrentElectionParam, nameof(CurrentElection))] public partial class ElectionViewModel : BaseViewModel { - private KeyCeremonyService _keyCeremonyService; - private ManifestService _manifestService; - private BallotUploadService _uploadService; - private ElectionService _electionService; - private TallyService _tallyService; - - public ElectionViewModel(IServiceProvider serviceProvider, KeyCeremonyService keyCeremonyService, ManifestService manifestService, BallotUploadService uploadService, ElectionService electionService, TallyService tallyService) : base(null, serviceProvider) + private readonly IStorageService _storageService; + private readonly IStorageService _driveService; + private readonly KeyCeremonyService _keyCeremonyService; + private readonly ManifestService _manifestService; + private readonly BallotUploadService _uploadService; + private readonly ElectionService _electionService; + private readonly TallyService _tallyService; + private readonly ConstantsService _constantsService; + private readonly ContextService _contextService; + + public ElectionViewModel( + IServiceProvider serviceProvider, + KeyCeremonyService keyCeremonyService, + ManifestService manifestService, + ContextService contextService, + ConstantsService constantsService, + BallotUploadService uploadService, + ElectionService electionService, + TallyService tallyService, + ZipStorageService zipStorageService, + IStorageService driveService) : base(null, serviceProvider) { _keyCeremonyService = keyCeremonyService; _manifestService = manifestService; _uploadService = uploadService; _electionService = electionService; _tallyService = tallyService; + _constantsService = constantsService; + _contextService = contextService; + _storageService = zipStorageService; + _driveService = driveService; } [ObservableProperty] @@ -123,12 +141,12 @@ partial void OnCurrentElectionChanged(Election? value) Tallies.Add(item); } - Step1Complete = CurrentElection.ExportEncryptionDateTime != null; + Step1Complete = CurrentElection?.ExportEncryptionDateTime != null; Step2Complete = BallotAddedTotal + BallotSpoiledTotal > 0; Step4Complete = Tallies.Count > 0; Step5Complete = Tallies.Count(t => t.LastExport != null) > 0; } - catch (Exception ex) + catch (Exception) { } }); @@ -145,8 +163,11 @@ private async Task CreateTally() await NavigationService.GoToPage(typeof(CreateTallyViewModel), pageParams); } - private bool CanCreateTally() => BallotCountTotal > 0 || BallotSpoiledTotal > 0; - + private bool CanCreateTally() + { + return BallotCountTotal > 0 || BallotSpoiledTotal > 0; + } + [RelayCommand(CanExecute = nameof(CanUpload))] private async Task AddBallots() { @@ -158,8 +179,10 @@ private async Task AddBallots() await NavigationService.GoToPage(typeof(BallotUploadViewModel), pageParams); } - private bool CanUpload() => CurrentElection?.ExportEncryptionDateTime != null; - + private bool CanUpload() + { + return CurrentElection?.ExportEncryptionDateTime != null; + } [RelayCommand(CanExecute = nameof(CanReview))] private async Task ReviewChallenged() @@ -167,26 +190,70 @@ private async Task ReviewChallenged() // add code to go to the Challenged ballot page } - private bool CanReview() => BallotSpoiledTotal > 0; - + private bool CanReview() + { + return BallotSpoiledTotal > 0; + } [RelayCommand] private async Task ExportEncryption() - { - var pageParams = new Dictionary - { - { BallotUploadViewModel.ElectionIdParam, CurrentElection.ElectionId } - }; - - // TODO create the zip for the election - - // await NavigationService.GoToPage(typeof(BallotUploadViewModel), pageParams); - await _electionService.UpdateExportDateAsync(CurrentElection.ElectionId, DateTime.Now); + { + const string egDriveLabel = "egdrive"; + + var answer = await Shell.Current.CurrentPage.DisplayAlert( + AppResources.ExportDriveWarningTitle, + AppResources.ExportDriveWarning, + AppResources.YesText, + AppResources.NoText); + + if (!answer) + { + return; + } + + // check for any usb drives named egDrive + var egDrives = from drive in DriveInfo.GetDrives() + where drive != null + where drive.DriveType == DriveType.Removable + where drive.VolumeLabel.ToLower() == egDriveLabel + select drive; + + var context = await _contextService.GetByElectionIdAsync(CurrentElection.ElectionId); + var constants = await _constantsService.GetByElectionIdAsync(CurrentElection.ElectionId); + var manifest = await _manifestService.GetByElectionIdAsync(CurrentElection.ElectionId); + + if (context == null || constants == null || manifest == null) + { + // there's a data problem. This should never happen. + return; + } + + var encryptionPackage = new EncryptionPackage(context, constants, manifest); + + foreach (var drive in egDrives) + { + const string artifactFolder = "artifacts"; + + var destinationFolder = Path.Combine(drive.Name, artifactFolder); + _ = Directory.CreateDirectory(destinationFolder); + + _driveService.UpdatePath(destinationFolder); + _driveService.ToFiles(encryptionPackage); + + } - // this is for testing only and will be removed - CurrentElection.ExportEncryptionDateTime = DateTime.Now; - AddBallotsCommand.NotifyCanExecuteChanged(); - Step1Complete = true; + if (egDrives.Count() >= 0) + { + await MarkCurrentElectionAsExported(); + } + } + + private async Task MarkCurrentElectionAsExported() + { + CurrentElection.ExportEncryptionDateTime = DateTime.UtcNow; + await _electionService.UpdateEncryptionExportDateAsync(CurrentElection.ElectionId, CurrentElection.ExportEncryptionDateTime.Value); + AddBallotsCommand.NotifyCanExecuteChanged(); + Step1Complete = true; } [RelayCommand] diff --git a/src/electionguard-ui/ElectionGuard.UI/ViewModels/ExportEncryptionViewModel.cs b/src/electionguard-ui/ElectionGuard.UI/ViewModels/ExportEncryptionViewModel.cs deleted file mode 100644 index de90d10bf..000000000 --- a/src/electionguard-ui/ElectionGuard.UI/ViewModels/ExportEncryptionViewModel.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.Input; - -namespace ElectionGuard.UI.ViewModels -{ - public partial class EncryptionPackageExportViewModel - : BaseViewModel - { - [ObservableProperty] - private bool _showPanel = false; - - [ObservableProperty] - private string _uploadText = string.Empty; - - [ObservableProperty] - private string _fileErrorMessage = string.Empty; - - [ObservableProperty] - private string _fileFolder = string.Empty; - - [ObservableProperty] - private string _folderErrorMessage = string.Empty; - - [ObservableProperty] - private string _resultsText = string.Empty; - - public EncryptionPackageExportViewModel( - IServiceProvider serviceProvider) - : base(null, serviceProvider) - { - } - - [RelayCommand] - private void Manual() - { - } - - [RelayCommand] - private void PickDeviceFile() { } - - [RelayCommand] - private void PickFolder() { } - - [RelayCommand] - private void Export() { } - - [RelayCommand] - private void Cancel() { } - - [RelayCommand] - private void Auto() { } - } -} diff --git a/src/electionguard-ui/ElectionGuard.UI/Views/EncryptionExportPage.xaml b/src/electionguard-ui/ElectionGuard.UI/Views/EncryptionExportPage.xaml deleted file mode 100644 index 0504732f1..000000000 --- a/src/electionguard-ui/ElectionGuard.UI/Views/EncryptionExportPage.xaml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - -