Temporary repo to track my changes on LTS functions app porting
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
14 KiB

  1. using Microsoft.Azure.Cosmos;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace CDP
  8. {
  9. internal class AuditDB
  10. {
  11. //https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients
  12. private static Lazy<CosmosClient> lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient);
  13. private static CosmosClient cosmosClient => lazyClient.Value;
  14. private static string DatabaseName = "CDP";
  15. private static string FileAuditContainer = "FileAudits";
  16. private static string UserAuditContainer = "UserAudits";
  17. private static string GroupAuditContainer = "GroupAudits";
  18. private static string TenantAuditContainer = "TenantAudits";
  19. private static CosmosClient InitializeCosmosClient()
  20. {
  21. // Perform any initialization here
  22. var uri = "https://cdplite.documents.azure.com:443/";
  23. var authKey = "VPbg8RpzyI3XwhC2o0dIUtYFs33ghxORCqZeNAyg8vg4HWUBjM41BUxP0qLFXEvFh6ewQY1uKv52ACDbsEN1AQ==";
  24. return new CosmosClient(uri, authKey);
  25. }
  26. public static async Task UpdateAuditDocument(AuditDocument al, string containerName)
  27. {
  28. if (al.Records.Count == 0)
  29. return;
  30. List<AuditDocument> lal = await SplitAuditlog(al);
  31. Container container = cosmosClient.GetContainer(DatabaseName, containerName);
  32. foreach (AuditDocument ial in lal)
  33. {
  34. ItemResponse<AuditDocument> r = await container.UpsertItemAsync(ial, new PartitionKey(ial.id));
  35. }
  36. await UpdateMetadata(container, lal);
  37. }
  38. static async Task UpdateMetadata(Container container, List<AuditDocument> lal)
  39. {
  40. bool update = false;
  41. string pKey = GetMetaDocumentKey(lal[0].DocId);
  42. MetadataDocument md = null;
  43. try
  44. {
  45. ItemResponse<MetadataDocument> response =
  46. await container.ReadItemAsync<MetadataDocument>(pKey, new PartitionKey(pKey));
  47. md = response.Resource;
  48. }
  49. catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
  50. {
  51. md = new MetadataDocument()
  52. {
  53. id = lal[0].DocId + "-meta",
  54. PartitionKeys = new List<string>()
  55. };
  56. }
  57. catch (Exception e)
  58. {
  59. // Helpers.LogIt(e.Message);
  60. return;
  61. }
  62. if (md == null)
  63. {
  64. // Helpers.LogIt("Something ugly happened!");
  65. return;
  66. }
  67. foreach (AuditDocument log in lal)
  68. {
  69. if (md.PartitionKeys.Contains(log.id))
  70. continue;
  71. md.PartitionKeys.Add(log.id);
  72. update = true;
  73. }
  74. if (update)
  75. {
  76. try
  77. {
  78. ItemResponse<MetadataDocument> r = await container.UpsertItemAsync(md, new PartitionKey(pKey));
  79. }
  80. catch (Exception e)
  81. {
  82. // Helpers.LogIt(e.Message);
  83. return;
  84. }
  85. }
  86. }
  87. public static async Task<AuditDocument> GetAuditDocument(string key, string ContainerName)
  88. {
  89. try
  90. {
  91. Container container = cosmosClient.GetContainer(DatabaseName, ContainerName);
  92. ItemResponse<AuditDocument> response = await container.ReadItemAsync<AuditDocument>(key, new PartitionKey(key));
  93. if (response == null)
  94. return null;
  95. AuditDocument t = response.Resource;
  96. return t;
  97. }
  98. catch (Exception e)
  99. {
  100. return null;
  101. }
  102. }
  103. static async Task<MetadataDocument> GetMetadataDocument(string id, string ContainerName)
  104. {
  105. MetadataDocument md = null;
  106. string pKey = GetMetaDocumentKey(id);
  107. PartitionKey partitionKey = new PartitionKeyBuilder()
  108. .Add(id)
  109. .Build();
  110. Container container = cosmosClient.GetContainer(DatabaseName, ContainerName);
  111. try
  112. {
  113. ItemResponse<MetadataDocument> response =
  114. await container.ReadItemAsync<MetadataDocument>(pKey, partitionKey);
  115. md = response.Resource;
  116. }
  117. catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
  118. {
  119. md = new MetadataDocument()
  120. {
  121. id = id,
  122. PartitionKeys = new List<string>()
  123. };
  124. }
  125. catch (Exception e)
  126. {
  127. // Helpers.LogIt(e.Message);
  128. return null;
  129. }
  130. if (md == null)
  131. {
  132. // Helpers.LogIt("Something ugly happened!");
  133. return null;
  134. }
  135. return md;
  136. }
  137. public static async Task<List<AuditRecord>> GetAuditRecordsBetweenDates(string DocId, DateTime startDate, DateTime endDate, string ContainerName)
  138. {
  139. var results = new List<AuditRecord>();
  140. Container container = cosmosClient.GetContainer(DatabaseName, ContainerName);
  141. // Fetch the metadata document for the customer ID
  142. var metadataDocumentId = GetMetaDocumentKey(DocId);
  143. MetadataDocument metadataDocument = null;
  144. try
  145. {
  146. var metadataDocumentResponse =
  147. await container.ReadItemAsync<MetadataDocument>(metadataDocumentId, new PartitionKey(metadataDocumentId));
  148. metadataDocument = metadataDocumentResponse.Resource;
  149. }
  150. catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
  151. {
  152. }
  153. catch (Exception e)
  154. {
  155. // Helpers.LogIt(e.Message);
  156. }
  157. if (metadataDocument == null)
  158. return results;
  159. // Determine the partition keys within the date range
  160. var partitionKeysInRange = metadataDocument.PartitionKeys
  161. .Where(pk => IsPartitionKeyInRange(pk, startDate, endDate))
  162. .ToList();
  163. // Fetch the audit records for each partition key within the date range
  164. foreach (var partitionKey in partitionKeysInRange)
  165. {
  166. ItemResponse<AuditDocument> response = await container.ReadItemAsync<AuditDocument>(partitionKey, new PartitionKey(partitionKey));
  167. if (response == null)
  168. continue;
  169. AuditDocument t = response.Resource;
  170. results.AddRange(t.Records);
  171. }
  172. return results;
  173. }
  174. static private bool IsPartitionKeyInRange(string partitionKey, DateTime startDate, DateTime endDate)
  175. {
  176. var partitionKeyParts = partitionKey.Split('-');
  177. var length = partitionKeyParts.Length;
  178. var year = int.Parse(partitionKeyParts[length - 3]);
  179. var dayOfYear = int.Parse(partitionKeyParts[length - 2]);
  180. var partitionKeyDate = new DateTime(year, 1, 1).AddDays(dayOfYear - 1);
  181. return partitionKeyDate >= startDate && partitionKeyDate <= endDate;
  182. }
  183. public static async Task<bool> AppendRecord(string id, AuditRecord rec, string ContainerName)
  184. {
  185. try
  186. {
  187. var metadataDocument = await GetMetadataDocument(id, ContainerName);
  188. if (metadataDocument == null)
  189. return false;
  190. string dayKey = metadataDocument.GetLatestKeyForDay(rec.EventTime);
  191. AuditDocument al = await GetAuditDocument(dayKey, ContainerName);
  192. if (al == null && ContainerName == FileAuditContainer)
  193. al = new AuditDocument() { DocId = rec.FileId };
  194. else if (al == null && ContainerName == TenantAuditContainer)
  195. al = new AuditDocument() { DocId = rec.AppKey };
  196. else if (al == null && ContainerName == UserAuditContainer)
  197. al = new AuditDocument() { DocId = rec.UserId };
  198. else if (al == null && ContainerName == GroupAuditContainer)
  199. al = new AuditDocument() { DocId = rec.GroupId };
  200. al.Records.Add(rec);
  201. await UpdateAuditDocument(al, ContainerName);
  202. return true;
  203. }
  204. catch (Exception e)
  205. {
  206. return false;
  207. }
  208. }
  209. public static async Task<bool> AppendRecordsList(string id, List<AuditRecord> rec, string ContainerName)
  210. {
  211. try
  212. {
  213. var metadataDocument = await GetMetadataDocument(id, ContainerName);
  214. if (metadataDocument == null || rec.Count <= 0)
  215. return false;
  216. string dayKey = metadataDocument.GetLatestKeyForDay(rec[0].EventTime);
  217. AuditDocument al = await GetAuditDocument(dayKey, ContainerName);
  218. /*if (al == null && ContainerName == FileAuditContainer)
  219. al = new AuditDocument() { DocId = rec.FileId };*/
  220. if (al == null && ContainerName == TenantAuditContainer)
  221. al = new AuditDocument() { DocId = rec[0].AppKey };
  222. else if (al == null && ContainerName == UserAuditContainer)
  223. al = new AuditDocument() { DocId = rec[0].UserId };
  224. rec.ForEach(record =>
  225. {
  226. al.Records.Add(record);
  227. });
  228. await UpdateAuditDocument(al, ContainerName);
  229. return true;
  230. }
  231. catch (Exception e)
  232. {
  233. return false;
  234. }
  235. }
  236. public static async Task<GroupAuditDocument> GetGroupAuditDocument(string appKey, string groupId)
  237. {
  238. Container container = cosmosClient.GetContainer(DatabaseName, GroupAuditContainer); // get the Group Container
  239. GroupAuditDocument ad = new GroupAuditDocument() { GroupId = groupId };
  240. try
  241. {
  242. // Store the unique identifier
  243. string id = groupId;
  244. PartitionKey partitionKey = new PartitionKeyBuilder()
  245. .Add(appKey)
  246. .Add(groupId)
  247. .Build();
  248. // Perform a point read
  249. ItemResponse<GroupAuditDocument> r = await container.ReadItemAsync<GroupAuditDocument>(id, partitionKey);
  250. return r;
  251. }
  252. catch (Exception e)
  253. {
  254. return null;
  255. }
  256. }
  257. public static async Task AddTenantAuditRecord(FileAuditRecord far)
  258. {
  259. TenantAuditRecord uar = new TenantAuditRecord()
  260. {
  261. Action = far.Action,
  262. AppKey = far.AppKey,
  263. EventTime = far.EventTime,
  264. FileId = far.FileId,
  265. GroupId = far.GroupId,
  266. Message = far.Message,
  267. UserId = far.UserId
  268. };
  269. // cosmosClient is init'd by the static on line 21
  270. Container container = cosmosClient.GetContainer(DatabaseName, TenantAuditContainer);
  271. // Build the full partition key path
  272. PartitionKey partitionKey = new PartitionKeyBuilder()
  273. .Add(uar.AppKey)
  274. .Build();
  275. TenantAuditDocument ad = await GetTenantAuditDocument(uar.AppKey);
  276. if (ad == null)
  277. ad = new TenantAuditDocument() { AppKey = uar.AppKey };
  278. ad.Records.Add(uar);
  279. try
  280. {
  281. ItemResponse<TenantAuditDocument> r = await container.UpsertItemAsync(ad, partitionKey);
  282. }
  283. catch (Exception e)
  284. {
  285. Console.WriteLine(e);
  286. }
  287. }
  288. public static async Task<TenantAuditDocument> GetTenantAuditDocument(string appKey)
  289. {
  290. Container container = cosmosClient.GetContainer(DatabaseName, TenantAuditContainer); // get the Group Container
  291. TenantAuditDocument ad = new TenantAuditDocument() { AppKey = appKey };
  292. try
  293. {
  294. // Store the unique identifier
  295. string id = appKey;
  296. PartitionKey partitionKey = new PartitionKeyBuilder()
  297. .Add(appKey)
  298. .Build();
  299. // Perform a point read
  300. ItemResponse<TenantAuditDocument> r = await container.ReadItemAsync<TenantAuditDocument>(id, partitionKey);
  301. return r;
  302. }
  303. catch (Exception e)
  304. {
  305. return null;
  306. }
  307. }
  308. static async Task<List<AuditDocument>> SplitAuditlog(AuditDocument al)
  309. {
  310. List<AuditDocument> lal = new List<AuditDocument>();
  311. var sortedRecords = al.Records.OrderBy(record => record.EventTime).ToList();
  312. var currentGroup = new List<AuditRecord>();
  313. var currentGroupSize = 0;
  314. int MaxDocumentSizeInBytes = 2 * 1024 * 1024; // 2MB
  315. int index = al.Index; // start for index passed in
  316. foreach (var record in sortedRecords)
  317. {
  318. var recordSize = record.CalculateRecordSize();
  319. if (currentGroupSize + recordSize > MaxDocumentSizeInBytes)
  320. {
  321. AuditDocument i = new AuditDocument();
  322. i.Index = index++;
  323. i.Records = currentGroup;
  324. lal.Add(i);
  325. currentGroup = new List<AuditRecord>();
  326. currentGroupSize = 0;
  327. }
  328. currentGroup.Add(record);
  329. currentGroupSize += recordSize;
  330. }
  331. if (currentGroup.Any())
  332. {
  333. AuditDocument i = new AuditDocument();
  334. i.DocId = al.DocId;
  335. i.Index = index++;
  336. i.Records = currentGroup;
  337. lal.Add(i);
  338. }
  339. return lal;
  340. }
  341. static string GetMetaDocumentKey(string id)
  342. {
  343. return $"{id}-meta";
  344. }
  345. }
  346. }