Code/SboxSuperuser/Engine/Audit/HostAuditService.Queries.cs

Part of HostAuditService handling queries and diagnostics. It authenticates and authorizes callers, loads audit storage, filters and paginates audit records, and returns audit pages or diagnostics.

File Access
using SboxSuperuser.Core.Audit;

namespace SboxSuperuser.Engine.Audit;

internal sealed partial class HostAuditService
{
	internal Task<AuditPage> QueryAsync( Func<ActorIdentity> authenticate, Func<ActorIdentity, bool> canReadAll,
		Func<ActorIdentity, bool> canReadOwn, AuditQuery query, CancellationToken cancellation )
	{
		ArgumentNullException.ThrowIfNull( query );
		return SerializedAsync( async () =>
		{
			var initial = authenticate();
			if ( initial is null || !canReadAll( initial ) && !canReadOwn( initial ) )
			{
				return new AuditPage( CommandStatus.Denied, "Audit access denied." );
			}

			if ( !await LoadAsync( cancellation ) )
			{
				return new AuditPage( CommandStatus.StorageUnavailable, "Audit storage is unavailable; existing evidence is preserved." );
			}

			var actor = authenticate();
			if ( actor is null || actor.Kind != initial.Kind || actor.SubjectId != initial.SubjectId )
			{
				return new AuditPage( CommandStatus.Denied, "Audit reader identity is no longer valid." );
			}

			var all = canReadAll( actor );
			if ( !all && (!canReadOwn( actor ) || actor.Kind != ActorKind.Player
				|| query.ActorSubject is not null && query.ActorSubject != actor.SubjectId) )
			{
				return new AuditPage( CommandStatus.Denied, "This caller may read only its own audit results." );
			}

			if ( query.Cursor is not null && query.Cursor.Revision != _revision )
			{
				return new AuditPage( CommandStatus.Conflict, "The audit journal changed; restart pagination." );
			}

			var now = _clock();
			var matches = _journal.Records.Where( record => (all || record.ActorKind == "Player" && record.ActorSubject == actor.SubjectId)
				&& (!_journal.IsExpired( record, now ) || _active.Contains( record.Id )) && Matches( record, query ) )
				.OrderByDescending( record => record.Sequence ).Take( query.PageSize + 1 ).ToArray();
			var selected = matches.Take( query.PageSize ).ToArray();
			var cursor = matches.Length > query.PageSize ? new AuditCursor( _revision, selected[ selected.Length - 1 ].Sequence ) : null;
			return new AuditPage( CommandStatus.Success, "Audit page read.", _revision, selected.Select( record => new AuditEntry( record ) ), cursor );
		}, status => new AuditPage( status, "Audit query could not complete." ), cancellation );
	}

	internal Task<AuditDiagnostics> ReadDiagnosticsAsync( Func<bool> authorize, CancellationToken cancellation )
	{
		return SerializedAsync( async () =>
		{
			if ( !authorize() )
			{
				return null;
			}

			await LoadAsync( cancellation );
			return authorize() ? Diagnostics() : null;
		}, _ => (AuditDiagnostics)null, cancellation );
	}

	private static bool Matches( AuditRecord record, AuditQuery query )
	{
		return (query.Cursor is null || record.Sequence < query.Cursor.BeforeSequence)
			&& (!query.RequestId.HasValue || record.RequestId == query.RequestId.Value)
			&& (query.Command is null || record.Command == query.Command)
			&& (query.ActorSubject is null || record.ActorSubject == query.ActorSubject)
			&& (!query.Outcome.HasValue || record.Outcome == query.Outcome.Value.ToString())
			&& (!query.Origin.HasValue || record.Origin == query.Origin.Value.ToString())
			&& (!query.FromUtc.HasValue || record.CreatedUtc >= query.FromUtc.Value)
			&& (!query.ToUtc.HasValue || record.CreatedUtc <= query.ToUtc.Value)
			&& (query.TargetSteamId is null || record.Targets.Contains( query.TargetSteamId, StringComparer.Ordinal ));
	}
}