Public Access
Server: send each item at the revision its ciphertext is sealed at, keep team archiving with the owner, refuse suspended accounts, and stop enrolment failing after an hour
- Pull hydrated every historical change entry with the item's current ciphertext under that entry's old revision. The AAD binds the revision, so a client opened it at the wrong version, failed, and parked its own pending edit as undecryptable. Entries an item has since moved past are left out; the later entry carries the same payload under the right number, and a fresh device no longer downloads an item once per edit it ever had. - Deleting a team's last vault archived the team whoever deleted it, so an admin could archive it out from under its owner. Only the owner's delete archives it now, and TeamService.ArchiveAsync checks. - UserStatus was checked nowhere on the request path. ActiveAccountRequirement on both policies answers 403 account-not-active for a suspended or deprovisioned account. - The hourly last-seen update moves xmin under the tracked user, and enrolment then saved that user against the stale token: a 500 for anybody enrolling more than an hour after first signing in. The entity is reloaded after the update.
This commit is contained in:
1 parent
f332d9a56f
commit
6846f67c20
14 files changed
+277
-12
No files matched your search
@@ -276,6 +276,52 @@ public sealed class SyncEndpointTests(ApiFixture fixture)
|
||||
second.Changes.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item edited several times is pulled once, at the revision its ciphertext is sealed at.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The table holds only the newest payload, and its AAD binds the newest version. Hydrating each older
|
||||
/// log entry with it paired revision 1 or 2 with a payload sealed at 3, which no client can open — and a
|
||||
/// client that could not open it parked its own pending edit as undecryptable.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task Pull_OfAnItemEditedSinceItsEarlierEntries_SendsOnlyItsCurrentRevision()
|
||||
{
|
||||
var (subject, vaultId) = await SeedUserWithVaultAsync();
|
||||
var client = fixture.CreateClientFor(subject);
|
||||
|
||||
var create = NewCreateBatch();
|
||||
var entityId = create.Operations[0].EntityId;
|
||||
await client.PostContractAsync(PushUrl(vaultId), create);
|
||||
|
||||
await client.PostContractAsync(PushUrl(vaultId), new SyncPushRequest(
|
||||
[
|
||||
NewOperation(entityId, expectedVersion: 1, envelope: [2, 2, 2]),
|
||||
]));
|
||||
|
||||
await client.PostContractAsync(PushUrl(vaultId), new SyncPushRequest(
|
||||
[
|
||||
NewOperation(entityId, expectedVersion: 2, envelope: [3, 3, 3]),
|
||||
]));
|
||||
|
||||
var pulled = await (await client.PostContractAsync(
|
||||
PullUrl(vaultId),
|
||||
new SyncPullRequest(null, null, null))).Content.ReadContractAsync<SyncPullResponse>();
|
||||
|
||||
pulled.ShouldNotBeNull();
|
||||
|
||||
var change = pulled.Changes.ShouldHaveSingleItem();
|
||||
change.Version.ShouldBe(3);
|
||||
change.Payload!.Envelope.ShouldBe([3, 3, 3]);
|
||||
|
||||
// And the cursor is past all three entries, so the next pull does not offer the old ones again.
|
||||
var next = await (await client.PostContractAsync(
|
||||
PullUrl(vaultId),
|
||||
new SyncPullRequest(pulled.NextCursor, null, null))).Content.ReadContractAsync<SyncPullResponse>();
|
||||
|
||||
next!.Changes.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Pull_WithACursorFromAnotherVault_Is400()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user