/* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using MongoDB.Bson; using MongoDB.Driver; using Rms.Risk.Mango.Interfaces; using Rms.Risk.Mango.Pivot.Core.MongoDb; using Rms.Risk.Mango.Pivot.UI.Services; namespace Rms.Risk.Mango.Services.Audit; // ReSharper disable InconsistentNaming public class AuditService(MongoDbConfigRecord _config, MongoDbSettings _settings, int _auditExpireDays, string? _databaseInstance = null) : IAuditService // ReSharper restore InconsistentNaming { public const string AuditCollection = DatabaseStructureLoader.AuditCollection; private readonly IMongoDatabase _database = MongoDbHelper.GetDatabase(_config, _settings, _databaseInstance); public void PreCheck(BsonDocument command) { if (command.ToJson().Contains(AuditCollection)) throw new ApplicationException("Forbidden command"); } public async Task Record(AuditRecord rec, CancellationToken token = default) { var commandType = rec.Command.ElementAt(0).Name ?? ""; if (MongoDbCommandHelper.IsReadOnlyCommand(commandType)) return; string? shortError = null; if (rec.Error != null) { var pos = rec.Error.IndexOf('\n'); if (pos >= 0) { shortError = rec.Error![..pos].Replace("\r", ""); shortError = shortError[..Math.Min(shortError.Length, 128)]; } } var doc = new BsonDocument(new Dictionary() { ["_id"] = Guid.NewGuid().ToString("N"), ["expire-at"] = DateTime.UtcNow + TimeSpan.FromDays(_auditExpireDays), ["ts"] = rec.Timestamp, ["collection"] = rec.Command.ElementAt(0).Value, ["database"] = rec.DatabaseName, ["email"] = rec.Email, ["ticket"] = rec.Ticket, ["ok"] = rec.Success, ["error"] = shortError, ["commandType"] = commandType, ["command"] = rec.Command }); await _database.GetCollection(AuditCollection).InsertOneAsync(doc, new (), token); } public async Task> Audit(DateTime startDate, DateTime endDate, CancellationToken token = default) { var filter = $@"{{ ""$and"" : [ {{ ts : {{ ""$gte"" : ISODate(""{startDate:yyyy-MM-dd}T00:00:00"") }} }}, {{ ts : {{ ""$lte"" : ISODate(""{endDate:yyyy-MM-dd}T23:59:59"") }} }} ] }}"; var findOptions = new FindOptions() { BatchSize = 1000 }; var coll = _database.GetCollection(AuditCollection); var cursor = await coll.FindAsync(filter, findOptions, token); var list = new List(); while (await cursor.MoveNextAsync(token)) { foreach (var doc in cursor.Current) { if (doc.IsBsonNull) continue; var rec = new AuditRecord ( DatabaseName: GetStr(doc,"database"), Timestamp : doc.GetValue("ts" , DateTime.MinValue).AsBsonDateTime.AsUniversalTime, Email : GetStr(doc,"email" ), Ticket : GetStr(doc,"ticket"), Success : doc.GetValue("ok" , false).ToBoolean(), Error : GetStr(doc,"error" ), Command : BsonDocument.Parse(doc.GetValue("command", "").ToString() ?? "") ); list.Add(rec); } } return list; } private static string GetStr(BsonDocument doc, string name) { if (!doc.Contains(name) || doc[name].IsBsonNull ) return ""; return doc[name].ToString() ?? ""; } //private static bool GetBool(BsonDocument doc, string name) //{ // if (!doc.Contains(name) || doc[name].IsBsonNull ) // return false; // return doc[name].ToBoolean(); //} }