/// <summary> /// Stopped /// </summary> public bool Stop() { this.Stopping?.Invoke(this, EventArgs.Empty); // Audit tool should never stop!!!!! if (!this.m_safeToStop) { AuditData securityAlertData = new AuditData(DateTime.Now, ActionType.Execute, OutcomeIndicator.EpicFail, EventIdentifierType.SecurityAlert, AuditUtil.CreateAuditActionCode(EventTypeCodes.UseOfARestrictedFunction)); AuditUtil.AddDeviceActor(securityAlertData); AuditUtil.SendAudit(securityAlertData); } this.Stopped?.Invoke(this, EventArgs.Empty); return(true); }
/// <summary> /// Fids the specified audit in the local repository /// </summary> public IEnumerable <AuditData> Find(Expression <Func <AuditData, bool> > query, int offset, int?count, out int totalResults) { try { var conn = this.CreateConnection(); using (conn.Lock()) { var builder = new QueryBuilder(this.m_mapper); var sql = builder.CreateQuery(query).Build(); sql = sql.OrderBy <DbAuditData>(o => o.Timestamp, SortOrderType.OrderByDescending); // Total results totalResults = conn.ExecuteScalar <Int32>($"SELECT COUNT(*) FROM ({sql.SQL})", sql.Arguments.ToArray()); // Query control if (count.HasValue) { sql.Limit(count.Value); } if (offset > 0) { if (count == 0) { sql.Limit(100).Offset(offset); } else { sql.Offset(offset); } } sql = sql.Build(); var itm = conn.Query <DbAuditData.QueryResult>(sql.SQL, sql.Arguments.ToArray()); AuditUtil.AuditAuditLogUsed(ActionType.Read, OutcomeIndicator.Success, sql.ToString(), itm.Select(o => new Guid(o.Id)).ToArray()); return(itm.Select(o => this.ToModelInstance(conn, o)).ToList()); } } catch (Exception e) { AuditUtil.AuditAuditLogUsed(ActionType.Read, OutcomeIndicator.EpicFail, query.ToString()); this.m_tracer.TraceError("Could not query audit {0}: {1}", query, e); throw; } }
/// <summary> /// Prune the audit database /// </summary> public void Prune() { var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>(); try { this.m_tracer.TraceInfo("Prune audits older than {0}", config?.AuditRetention); if (config?.AuditRetention == null) { return; // keep audits forever } var conn = this.CreateConnection(); using (conn.Lock()) { try { conn.BeginTransaction(); DateTime cutoff = DateTime.Now.Subtract(config.AuditRetention); Expression <Func <DbAuditData, bool> > epred = o => o.CreationTime < cutoff; conn.Table <DbAuditData>().Delete(epred); // Delete objects conn.Execute($"DELETE FROM {conn.GetMapping<DbAuditObject>().TableName} WHERE NOT({conn.GetMapping<DbAuditObject>().FindColumnWithPropertyName(nameof(DbAuditObject.AuditId)).Name} IN " + $"(SELECT {conn.GetMapping<DbAuditData>().FindColumnWithPropertyName(nameof(DbAuditData.Id)).Name} FROM {conn.GetMapping<DbAuditData>().TableName})" + ")"); AuditUtil.AuditAuditLogUsed(ActionType.Delete, OutcomeIndicator.Success, epred.ToString()); conn.Commit(); } catch { conn.Rollback(); } } } catch (Exception e) { this.m_tracer.TraceError("Error pruning audit database: {0}", e); throw; } }
/// <summary> /// Get the specified audit data from the database /// </summary> public AuditData Get(object id) { try { Guid pk = Guid.Empty; if (id is Guid) { pk = (Guid)id; } else if (id is String) { pk = Guid.Parse(id.ToString()); } else { throw new ArgumentException("Parameter must be GUID or parsable as GUID", nameof(id)); } // Fetch var conn = this.CreateConnection(); using (conn.Lock()) { var builder = new QueryBuilder(this.m_mapper); var sql = builder.CreateQuery <AuditData>(o => o.CorrelationToken == pk).Limit(1).Build(); var res = conn.Query <DbAuditData.QueryResult>(sql.SQL, sql.Arguments.ToArray()).FirstOrDefault(); AuditUtil.AuditAuditLogUsed(ActionType.Read, OutcomeIndicator.Success, sql.ToString(), pk); return(this.ToModelInstance(conn, res, false)); } } catch (Exception e) { AuditUtil.AuditAuditLogUsed(ActionType.Read, OutcomeIndicator.EpicFail, id.ToString()); this.m_tracer.TraceError("Error retrieving audit {0} : {1}", id, e); throw; } }
/// <summary> /// Start auditor service /// </summary> public bool Start() { this.Starting?.Invoke(this, EventArgs.Empty); this.m_safeToStop = false; ApplicationContext.Current.Started += (o, e) => { try { this.m_tracer.TraceInfo("Binding to service events..."); ApplicationContext.Current.GetService <IIdentityProviderService>().Authenticated += (so, se) => { if ((se.Principal?.Identity.Name ?? se.UserName).ToLower() != ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName.ToLower()) { AuditUtil.AuditLogin(se.Principal, se.UserName, so as IIdentityProviderService, se.Success); } }; ApplicationContext.Current.GetService <QueueManagerService>().QueueExhausted += (so, se) => { if (se.ObjectKeys.Count() > 0) { switch (se.Queue) { case "inbound": if (SynchronizationQueue.Inbound.Count() == 0) { AuditUtil.AuditDataAction <IdentifiedData>(EventTypeCodes.Import, ActionType.Create, AuditableObjectLifecycle.Import, EventIdentifierType.Import, OutcomeIndicator.Success, null); } break; case "outbound": if (SynchronizationQueue.Outbound.Count() == 0) { AuditUtil.AuditDataAction <IdentifiedData>(EventTypeCodes.Export, ActionType.Execute, AuditableObjectLifecycle.Export, EventIdentifierType.Export, OutcomeIndicator.Success, null); } break; } } }; // Scan for IRepositoryServices and bind to their events as well foreach (var svc in ApplicationContext.Current.GetServices().OfType <IAuditEventSource>()) { svc.DataCreated += (so, se) => { if (se.Objects.Any(x => x is Entity || x is Act) && AuthenticationContext.Current.Principal.Identity.Name.ToLower() != ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName.ToLower()) { AuditUtil.AuditDataAction(EventTypeCodes.PatientRecord, ActionType.Create, AuditableObjectLifecycle.Creation, EventIdentifierType.PatientRecord, se.Success ? OutcomeIndicator.Success : OutcomeIndicator.SeriousFail, null, se.Objects.OfType <IdentifiedData>().ToArray()); } }; svc.DataUpdated += (so, se) => { if (se.Objects.Any(x => x is Entity || x is Act) && AuthenticationContext.Current.Principal.Identity.Name.ToLower() != ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName.ToLower()) { AuditUtil.AuditDataAction(EventTypeCodes.PatientRecord, ActionType.Update, AuditableObjectLifecycle.Amendment, EventIdentifierType.PatientRecord, se.Success ? OutcomeIndicator.Success : OutcomeIndicator.SeriousFail, null, se.Objects.OfType <IdentifiedData>().ToArray()); } }; svc.DataObsoleted += (so, se) => { if (se.Objects.Any(x => x is Entity || x is Act) && AuthenticationContext.Current.Principal.Identity.Name.ToLower() != ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName.ToLower()) { AuditUtil.AuditDataAction(EventTypeCodes.PatientRecord, ActionType.Delete, AuditableObjectLifecycle.LogicalDeletion, EventIdentifierType.PatientRecord, se.Success ? OutcomeIndicator.Success : OutcomeIndicator.SeriousFail, null, se.Objects.OfType <IdentifiedData>().ToArray()); } }; svc.DataDisclosed += (so, se) => { if (se.Objects.Count() > 0 && se.Objects.Any(i => i is Patient || i is Act) && AuthenticationContext.Current.Principal.Identity.Name.ToLower() != ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName.ToLower() && AuthenticationContext.Current.Principal.Identity.Name.ToLower() != "system") { AuditUtil.AuditDataAction(EventTypeCodes.Query, ActionType.Read, AuditableObjectLifecycle.Disclosure, EventIdentifierType.Query, se.Success ? OutcomeIndicator.Success : OutcomeIndicator.SeriousFail, se.Query, se.Objects.OfType <IdentifiedData>().ToArray()); } }; if (svc is ISecurityAuditEventSource) { (svc as ISecurityAuditEventSource).SecurityAttributesChanged += (so, se) => AuditUtil.AuditSecurityAttributeAction(se.Objects, se.Success, se.ChangedProperties); } } AuditUtil.AuditApplicationStartStop(EventTypeCodes.ApplicationStart); } catch (Exception ex) { this.m_tracer.TraceError("Error starting up audit repository service: {0}", ex); } }; ApplicationContext.Current.Stopped += (o, e) => AuditUtil.AuditApplicationStartStop(EventTypeCodes.ApplicationStop); ApplicationContext.Current.Stopping += (o, e) => this.m_safeToStop = true; AuditInfo sendAudit = new AuditInfo(); // Send audit Action <Object> timerQueue = null; timerQueue = o => { lock (sendAudit) if (sendAudit.Audit.Count > 0) { SynchronizationQueue.Admin.Enqueue(new AuditInfo() { Audit = new List <AuditData>(sendAudit.Audit) }, Synchronization.Model.DataOperationType.Insert); sendAudit.Audit.Clear(); } ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(new TimeSpan(0, 0, 30), timerQueue, null); }; // Queue user work item for sending ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(new TimeSpan(0, 0, 30), timerQueue, null); // Queue pooled item ApplicationContext.Current.GetService <IThreadPoolService>().QueueNonPooledWorkItem(o => { while (!this.m_safeToStop) { try { this.m_resetEvent.WaitOne(); while (this.m_auditQueue.Count > 0) { AuditData ad = null; lock (this.m_auditQueue) ad = this.m_auditQueue.Dequeue(); try { // First, save the audit locally var ar = ApplicationContext.Current.GetService <IAuditRepositoryService>(); if (ar == null) { throw new InvalidOperationException("!!SECURITY ALERT!! >> Cannot find audit repository"); } ad = ar.Insert(ad); lock (sendAudit) sendAudit.Audit.Add(ad); } catch (Exception e) { this.m_tracer.TraceError("!!SECURITY ALERT!! >> Error sending audit {0}: {1}", ad, e); } } } catch (Exception e) { this.m_tracer.TraceError("!!SECURITY ALERT!! >> Error polling audit task list {0}", e); } } }, null); this.Started?.Invoke(this, EventArgs.Empty); return(true); }