CultNetDatabaseServer: a peer's put or delete commits only when its write authority grants it
CultNetDatabaseServerOptions.RemoteWriteAuthority is asked once per received write, after the schema resolves and before the shard does; none configured refuses every remote write; an authority that throws refuses and is logged. A refusal applies, publishes, logs and forwards nothing. The host's own CultNetDatabase.ApplyPutAsync/ApplyDeleteAsync ask no remote authority. Existing server tests whose subject is not the grant pass a test-local granting authority. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf1e4f7a9d
commit
b4f494b7a0
8 changed files with 480 additions and 10 deletions
|
|
@ -288,11 +288,52 @@ namespace GameCult.Networking
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// The one question asked of the write authority per received write. None configured refuses; an authority that throws
|
||||
// refuses too (fail closed) and the fault is logged as an error.
|
||||
private Action<string, CultRecordKey> Authorize(
|
||||
CultNetServerPeer peer,
|
||||
CultNetRemoteWriteOperation operation,
|
||||
string? shardId,
|
||||
long? shardEpoch)
|
||||
{
|
||||
return (schemaId, key) =>
|
||||
{
|
||||
var request = new CultNetRemoteWriteRequest(peer, operation, schemaId, key, shardId, shardEpoch);
|
||||
var authority = _options.RemoteWriteAuthority;
|
||||
if (authority == null)
|
||||
{
|
||||
throw new CultNetRemoteWriteRefusedException(request, "no remote write authority is configured");
|
||||
}
|
||||
|
||||
CultNetRemoteWriteDecision decision;
|
||||
try
|
||||
{
|
||||
decision = authority.Decide(request);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_server.Logger.LogError($"CultNet remote write authority failed: {ex.Message}");
|
||||
throw new CultNetRemoteWriteRefusedException(request, "the write authority failed");
|
||||
}
|
||||
|
||||
if (decision == null || !decision.IsGranted)
|
||||
{
|
||||
throw new CultNetRemoteWriteRefusedException(request, decision?.Reason);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task HandlePutAsync(CultNetDocumentPutRawMessage message, CultNetServerPeer peer)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _database.ApplyPutAsync(message).ConfigureAwait(false);
|
||||
await _database.ApplyPutAsync(message, Authorize(peer, CultNetRemoteWriteOperation.Put, message.ShardId, message.ShardEpoch))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (CultNetRemoteWriteRefusedException ex)
|
||||
{
|
||||
_server.Logger.LogWarning($"CultNet raw put refused: {ex.Message}");
|
||||
peer.SendCultNet(CultNetErrorMessage.ForRemoteWriteRefused(ex.Request, ex.Reason));
|
||||
}
|
||||
catch (CultNetShardAuthorityException ex)
|
||||
{
|
||||
|
|
@ -323,7 +364,13 @@ namespace GameCult.Networking
|
|||
{
|
||||
try
|
||||
{
|
||||
await _database.ApplyDeleteAsync(message).ConfigureAwait(false);
|
||||
await _database.ApplyDeleteAsync(message, Authorize(peer, CultNetRemoteWriteOperation.Delete, message.ShardId, message.ShardEpoch))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (CultNetRemoteWriteRefusedException ex)
|
||||
{
|
||||
_server.Logger.LogWarning($"CultNet raw delete refused: {ex.Message}");
|
||||
peer.SendCultNet(CultNetErrorMessage.ForRemoteWriteRefused(ex.Request, ex.Reason));
|
||||
}
|
||||
catch (CultNetShardAuthorityException ex)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue