Hermod
A cross-platform, modular and fully GDPR-compliant email archival solution!
Loading...
Searching...
No Matches
JsonDatabaseConnector.cs
Go to the documentation of this file.
1using System;
2
4
5 using Core.Accounts;
6 using Core.Exceptions;
7
8 using Newtonsoft.Json;
9
10 using System.IO;
11 using System.Security.Cryptography;
12
16 public partial class JsonDatabaseConnector: DatabaseConnector {
17
18 public class DomainContainer {
19 public List<Domain> DomainList { get; set; } = new List<Domain>();
20 }
21
22 internal FileInfo JsonFile { get; set; }
23
25
26 public JsonDatabaseConnector(FileInfo jsonFile, byte[] key, byte[] initVector) {
27 if (jsonFile is null) {
28 throw new ArgumentNullException(nameof(jsonFile));
29 }
30 if (key is null || key.Length == 0) {
31 throw new ArgumentNullException(nameof(key));
32 }
33 if (initVector is null || initVector.Length == 0) {
34 throw new ArgumentNullException(nameof(initVector));
35 }
36
37 JsonFile = jsonFile;
38 m_encKey = key;
39 m_initVector = initVector;
40 }
41
43 DumpJson();
44 }
45
47 public override void Connect() {
48 if (!JsonFile.Exists) {
49 DumpJson();
50 return;
51 }
52 ReadFile();
53 }
54
56 public override async Task ConnectAsync() {
57 if (!JsonFile.Exists) {
58 await DumpJsonAsync();
59 return;
60 }
61 await ReadFileAsync();
62 }
63
64 internal void ReadFile() {
65 var fContents = File.ReadAllBytes(JsonFile.FullName);
66 if (fContents is null) {
67 DumpJson();
68 return;
69 }
70
71 string decryptedString = DecryptString(fContents);
72 if (decryptedString is null) {
73 DumpJson();
74 return;
75 }
76
77 m_jsonObj = JsonConvert.DeserializeObject<DomainContainer>(decryptedString);
78 }
79
80 internal async Task ReadFileAsync() {
81 var fContents = await File.ReadAllBytesAsync(JsonFile.FullName);
82 if (fContents is null) {
83 await DumpJsonAsync();
84 return;
85 }
86
87 string decryptedString = await DecryptStringAsync(fContents);
88 if (decryptedString is null) {
89 await DumpJsonAsync();
90 return;
91 }
92
93 m_jsonObj = JsonConvert.DeserializeObject<DomainContainer>(decryptedString);
94 }
95
96 internal void DumpJson() {
97 using var fStream = JsonFile.Create();
98 using var sWriter = new StreamWriter(fStream);
99 sWriter.Write(EncryptString(JsonConvert.SerializeObject(m_jsonObj, Formatting.Indented)));
100 }
101
102 internal async Task DumpJsonAsync() {
103 using var fStream = JsonFile.Create();
104 using var sWriter = new StreamWriter(fStream);
105 sWriter.Write(await EncryptStringAsync(JsonConvert.SerializeObject(m_jsonObj, Formatting.Indented)));
106 }
107
109 public override Task<List<Domain>> GetDomainsAsync(bool includeUsers = true, params string[] tlds) {
110 if (tlds.Length == 0) {
111 return Task.FromResult(
112 includeUsers ?
114 new List<Domain>(m_jsonObj.DomainList.Select(x => new Domain(x)))
115 );
116 }
117
118 var filteredDomains = m_jsonObj.DomainList.Where(d => tlds.Contains(d.Tld)).ToList();
119
120 return Task.FromResult(
121 includeUsers ?
122 filteredDomains :
123 new List<Domain>(filteredDomains.Select(x => new Domain(x)))
124 );
125 }
126
128 public override Task GetUsersForDomainAsync(Domain domain) {
129 domain.DomainUsers = m_jsonObj.DomainList.First(x => x.DomainName == domain.DomainName && domain.Tld == x.Tld).DomainUsers.ToList();
130
131 return Task.CompletedTask;
132 }
133
135 public override async Task InitialiseDatabaseAsync() {
136 await DumpJsonAsync();
137 }
138
140 public override Task<bool> IsInitialisedAsync() => Task.FromResult(true); // this is always true here
141
146 public override async Task<int> PurgeDatabasesAsync() {
147 var domainCount = m_jsonObj.DomainList.Count;
149 await DumpJsonAsync();
150
151 return domainCount;
152 }
153
158 public override async Task<int> PurgeUsersFromDomainAsync(Domain domain) {
159 var domainToPurge = m_jsonObj.DomainList.First(d => d.Tld == domain.Tld && d.DomainName == domain.DomainName);
160 var domainUsers = domainToPurge.DomainUsers.Count;
161 domainToPurge.DomainUsers = new List<DomainUser>();
162 await DumpJsonAsync();
163
164 return domainUsers;
165 }
166
168 public override async Task<bool> RemoveUserFromDomainAsync(Domain domain, DomainUser user) {
169 var domainToEdit = m_jsonObj.DomainList.First(d => d.Tld == domain.Tld && d.DomainName == domain.DomainName);
170 domainToEdit.DomainUsers.Remove(user);
171 await DumpJsonAsync();
172
173 return true;
174 }
175
177 public override async Task<Domain> AddDomainAsync(string domainName) {
178 string? tld;
179 string? domain;
181
182 if (!Domain.IsValidDomain(domainName, out _, out tld, out domain)) {
183 if (tld is null || domain is null) {
184 throw new InvalidDomainNameException(domainName, "Encountered malformed domain name!");
185 }
186 } else if (tld is null || domain is null) {
187 throw new Exception("An unknown exception has occurred!"); // this should never happen
188 }
189
190 var newDomain = new Domain(m_jsonObj.DomainList.Count + 1, tld, domainName);
191 m_jsonObj.DomainList.Add(newDomain);
192 await DumpJsonAsync();
193
194 return newDomain;
195 }
196
197 public override Task<bool> RemoveDomainAsync(Domain domain) {
198 throw new NotImplementedException();
199 }
200 }
201}
202
Represents a single domain.
Definition: Domain.cs:20
string DomainName
The name of the domain.
Definition: Domain.cs:105
string Tld
The top-level domain.
Definition: Domain.cs:97
static bool IsValidDomain(string fqdn, out int? tldLevels, out string? tld, out string? domain)
Gets a value indicating whether or not a given domain is valid.
Definition: Domain.cs:133
static async Task< bool > DownloadCurrentValidTldListFromIanaAsync()
Asynchronously downloads the latest list of TLDs from the IANA.
Definition: Domain.cs:225
This class represents a single user (email account) in a domain.
Definition: DomainUser.cs:11
Exception class that is thrown when an invalid domain name is encountered.
override async Task InitialiseDatabaseAsync()
Asynchronously initialises the database.An awaitable Task
async Task< byte[]> EncryptStringAsync(string plaintext)
Asynchronously encrypts a given string.
JsonDatabaseConnector(FileInfo jsonFile, byte[] key, byte[] initVector)
async Task< string > DecryptStringAsync(byte[] bytes)
Asynchronously decrypts a cipher text to a string.
override async Task< Domain > AddDomainAsync(string domainName)
Adds a single domain to the database.An awaitable Task<Domain> containing the newly generated domain.
byte[] EncryptString(string plaintext)
Encrypts a string.
override Task GetUsersForDomainAsync(Domain domain)
Asynchronously gets all the users for a given domain.An awaitable Task
override void Connect()
Connect to the database.
override async Task< int > PurgeUsersFromDomainAsync(Domain domain)
override async Task< bool > RemoveUserFromDomainAsync(Domain domain, DomainUser user)
Asynchronously removes a single user from a given domain.An awaitable Task<Boolean> indicating whethe...
string DecryptString(byte[] bytes)
Decrypts a cipher text to a string.
override Task< bool > RemoveDomainAsync(Domain domain)
Removes a single domain from the database.
override async Task ConnectAsync()
Asynchronously connect to the database.An awaitable Task
override Task< bool > IsInitialisedAsync()
Asynchronously gets a value indicating whether or not the database is initialised....
override Task< List< Domain > > GetDomainsAsync(bool includeUsers=true, params string[] tlds)
Asynchronously gets a list of all known domains.An awaitable Task<List<Domain>> containing the search...