feat(api): bind encrypted workspaces to accounts and devices
This commit is contained in:
1 parent
019b15041a
commit
c7d8ef15bc
49 files changed
+1904
-278
No files matched your search
@@ -20,8 +20,8 @@ public class ChannelServiceTests : IDisposable
|
||||
private static void AssertCode(ErrorCode code, Action act) =>
|
||||
Assert.Equal(code, Assert.Throws<WebDropException>(act).Code);
|
||||
|
||||
private Task<AppendResult> Append(TestChannel ch, Credentials who, byte[] frame, long? maxDownloads = null, string? label = null) =>
|
||||
Svc.AppendFrameAsync(ch.Id, who, frame, maxDownloads, label);
|
||||
private Task<AppendResult> Append(TestChannel ch, Credentials who, byte[] frame, long? maxDownloads = null) =>
|
||||
Svc.AppendFrameAsync(ch.Id, who, frame, maxDownloads);
|
||||
|
||||
private Task<AppendResult> AppendSmall(TestChannel ch, Credentials who, Id16? item = null, byte fill = 0x42, FrameFlags extra = FrameFlags.None) =>
|
||||
Append(ch, who, TestEnv.Frame(item ?? TestEnv.NewId(), 0, TestEnv.SmallEnc | extra, TestEnv.EncPayload, fill));
|
||||
@@ -44,7 +44,7 @@ public class ChannelServiceTests : IDisposable
|
||||
ReadHash = Codec.Encode(TokenHash.Compute(ch.ReadToken)),
|
||||
OwnerHash = Codec.Encode(TokenHash.Compute(ch.OwnerToken)),
|
||||
};
|
||||
AssertCode(ErrorCode.Conflict, () => Svc.CreateChannel(dup, _ownerEp));
|
||||
AssertCode(ErrorCode.Conflict, () => Svc.CreateChannel(dup, _ownerEp, "owner"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -55,9 +55,9 @@ public class ChannelServiceTests : IDisposable
|
||||
var encNoRead = new CreateChannelRequest { Id = TestEnv.NewId().ToBase64Url(), Mode = ChannelMode.Enc, OwnerHash = h() };
|
||||
var noOwner = new CreateChannelRequest { Id = TestEnv.NewId().ToBase64Url(), Mode = ChannelMode.Raw };
|
||||
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(rawWithRead, _ownerEp));
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(encNoRead, _ownerEp));
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(noOwner, _ownerEp));
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(rawWithRead, _ownerEp, "owner"));
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(encNoRead, _ownerEp, "owner"));
|
||||
AssertCode(ErrorCode.BadRequest, () => Svc.CreateChannel(noOwner, _ownerEp, "owner"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -60,7 +60,7 @@ public class EnvelopeTests
|
||||
public void RejectsReservedFlagBits()
|
||||
{
|
||||
var bytes = TestEnv.Frame(TestEnv.NewId(), 0, FrameFlags.Final, 4);
|
||||
bytes[1] |= 0x10;
|
||||
bytes[1] |= 0x20;
|
||||
Assert.False(Envelope.TryParse(bytes, out _));
|
||||
}
|
||||
|
||||
@@ -110,8 +110,17 @@ public class AuthTests
|
||||
|
||||
public class PolicyTests
|
||||
{
|
||||
[Fact]
|
||||
public void AuthenticatedAuthorsCanManageOwnItemsAcrossDevices()
|
||||
{
|
||||
var first = TestEnv.Endpoint(); var second = TestEnv.Endpoint();
|
||||
var item = ItemBy(first) with { AuthorAccount = "member" };
|
||||
var channel = Chan(new ChannelSettings { DeletePolicy = DeletePolicy.Own });
|
||||
Assert.True(ChannelPolicy.CanDeleteItem(channel, item, new Principal(Role.Read, second, "member")));
|
||||
Assert.False(ChannelPolicy.CanDeleteItem(channel, item, new Principal(Role.Read, first, "other-account")));
|
||||
}
|
||||
private static Channel Chan(ChannelSettings s, bool writeHash = true) => new(
|
||||
TestEnv.NewId(), ChannelMode.Enc, s, new byte[32], writeHash ? new byte[32] : null, new byte[32], 0, 0, 0, 0, 0);
|
||||
TestEnv.NewId(), ChannelMode.Enc, s, new byte[32], writeHash ? new byte[32] : null, new byte[32], 0, 0, 0, 0, 0, "owner");
|
||||
|
||||
private static Item ItemBy(byte[] author) => new(
|
||||
TestEnv.NewId(), TestEnv.NewId(), author, FrameFlags.None, 0, 0, 0, -1, null, 0, 0, 0, null, null);
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class RecordingEvents : IChannelEvents
|
||||
}
|
||||
|
||||
/// <summary>一个频道的全部凭据,模拟客户端从 K / A 派生出的令牌。</summary>
|
||||
public sealed class TestChannel(Id16 id, ChannelMode mode, byte[] read, byte[] write, byte[] owner)
|
||||
public sealed class TestChannel(Id16 id, ChannelMode mode, byte[] read, byte[] write, byte[] owner, byte[] ownerEndpoint)
|
||||
{
|
||||
public Id16 Id { get; } = id;
|
||||
public ChannelMode Mode { get; } = mode;
|
||||
@@ -30,9 +30,9 @@ public sealed class TestChannel(Id16 id, ChannelMode mode, byte[] read, byte[] w
|
||||
public byte[] WriteToken { get; } = write;
|
||||
public byte[] OwnerToken { get; } = owner;
|
||||
|
||||
public Credentials Owner(byte[] endpoint) => new(Role.Owner, OwnerToken, endpoint);
|
||||
public Credentials Member(byte[] endpoint) => new(Role.Write, WriteToken, endpoint);
|
||||
public Credentials Reader(byte[] endpoint) => new(Role.Read, ReadToken, endpoint);
|
||||
public Credentials Owner(byte[] endpoint) => new(Role.Owner, OwnerToken, endpoint, "owner");
|
||||
public Credentials Member(byte[] endpoint) => new(Role.Write, WriteToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null);
|
||||
public Credentials Reader(byte[] endpoint) => new(Role.Read, ReadToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null);
|
||||
}
|
||||
|
||||
public sealed class TestEnv : IDisposable
|
||||
@@ -76,9 +76,9 @@ public sealed class TestEnv : IDisposable
|
||||
ReadHash = mode == ChannelMode.Enc ? Codec.Encode(TokenHash.Compute(read)) : null,
|
||||
WriteHash = withWriteHash ? Codec.Encode(TokenHash.Compute(write)) : null,
|
||||
OwnerHash = Codec.Encode(TokenHash.Compute(owner)),
|
||||
}, ownerEndpoint);
|
||||
}, ownerEndpoint, "owner");
|
||||
|
||||
return new TestChannel(id, mode, read, write, owner);
|
||||
return new TestChannel(id, mode, read, write, owner, ownerEndpoint);
|
||||
}
|
||||
|
||||
/// <summary>构造一个封装。enc 模式的负载长度必须是 档位+16。</summary>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using WebDrop.Core;
|
||||
|
||||
namespace WebDrop.Core.Tests;
|
||||
public sealed class WorkspaceControlTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ChannelMode.Enc)]
|
||||
[InlineData(ChannelMode.Raw)]
|
||||
public async Task ThreadStructureSurvivesContentExpiryAndEviction(ChannelMode mode)
|
||||
{
|
||||
using var env = new TestEnv(); var endpoint = TestEnv.Endpoint();
|
||||
var channel = env.Create(endpoint, mode, new ChannelSettings { TtlSeconds = 60, MaxItems = 1 });
|
||||
var id = TestEnv.NewId(); var control = FrameFlags.Final | FrameFlags.Control | (mode == ChannelMode.Enc ? FrameFlags.Enc : 0);
|
||||
var normal = FrameFlags.Final | (mode == ChannelMode.Enc ? FrameFlags.Enc : 0);
|
||||
int payload = mode == ChannelMode.Enc ? TestEnv.EncPayload : 16;
|
||||
await env.Service.AppendFrameAsync(channel.Id, channel.Owner(endpoint), TestEnv.Frame(id, 0, control, payload), null);
|
||||
env.Clock.Advance(TimeSpan.FromSeconds(59));
|
||||
for (int i = 0; i < 3; i++) await env.Service.AppendFrameAsync(channel.Id, channel.Owner(endpoint), TestEnv.Frame(TestEnv.NewId(), 0, normal, payload), null);
|
||||
env.Clock.Advance(TimeSpan.FromSeconds(2)); env.Service.Sweep();
|
||||
Assert.NotNull(env.Store.Read(tx => tx.GetItem(channel.Id, id)));
|
||||
Assert.Equal(1, env.Store.Read(tx => tx.CountControlItems(channel.Id)));
|
||||
Assert.Equal(1, env.Store.Read(tx => tx.CountItems(channel.Id)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user