refactor(host): single-source ChannelPolicy, space-signed file grants, whole-file registration
- all authorization rules (owner/creator, per-channel read/write grants, who may remove whom, delete rules, structure management) live in ChannelPolicy; a test fails if they are reimplemented elsewhere - ChannelInfo carries what the caller may do (can_*), member and invitation lists say who can be removed; clients display it and never derive it - the host no longer stores or calls the file service: it only signs short-lived tickets from space permissions (create/put/get/delete/domain), and keeps an opaque fileDomain reference on the channel - a whole file is registered in one request (last frame header + per-frame length and digest) instead of one request per 1 MiB frame; the signed manifest still has to match every digest - drop file quota/threshold, receipts, reconcile and delete-queue code from the host - contract generator emits constants for enums, error codes, token roles, permissions and share states
This commit is contained in:
1 parent
ccb2c074d6
commit
17638b6e20
36 files changed
+926
-589
No files matched your search
@@ -148,4 +148,54 @@ public sealed class WorkspaceStructureTests
|
||||
Assert.Throws<WebDropException>(() => env.Service.CreateWorkspaceChannel(ch.Id, alice, "新频道", null)); // 新建频道涉及密钥,只有空间主
|
||||
Assert.Throws<WebDropException>(() => env.Service.CreateWorkspaceRoleResult(ch.Id, alice, "自封", null, null, null, []));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChannelFileDomainIsAnOpaqueReferenceSetOnceAndNeverInterpreted()
|
||||
{
|
||||
using var env = new TestEnv(); var endpoint = TestEnv.Endpoint(); var ch = env.Create(endpoint); var owner = ch.Owner(endpoint);
|
||||
Assert.Null(env.Service.GetWorkspace(ch.Id, owner).Channels.Single().FileDomain);
|
||||
var created = env.Service.CreateWorkspaceChannelResult(ch.Id, owner, "文件", null, "ab12-CD_34").Channel;
|
||||
Assert.Equal("ab12-CD_34", created.FileDomain);
|
||||
Assert.Throws<WebDropException>(() => env.Service.CreateWorkspaceChannelResult(ch.Id, owner, "乱", null, "bad domain!"));
|
||||
|
||||
// 创建时没带的,以后可以补一次;补了就不能换成别的(文件已经存在那里了)
|
||||
var later = env.Service.CreateWorkspaceChannelResult(ch.Id, owner, "稍后", null).Channel;
|
||||
env.Service.SetWorkspaceChannelFileDomain(ch.Id, owner, later.Id, "dom-1");
|
||||
env.Service.SetWorkspaceChannelFileDomain(ch.Id, owner, later.Id, "dom-1");
|
||||
Assert.Throws<WebDropException>(() => env.Service.SetWorkspaceChannelFileDomain(ch.Id, owner, later.Id, "dom-2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChannelInfoStatesWhatTheCallerMayDoSoClientsNeverDeriveIt()
|
||||
{
|
||||
var (env, ch, owner, alice, bob, carol) = RoleSpace(); using var _env = env;
|
||||
var admins = env.Service.CreateWorkspaceRoleResult(ch.Id, owner, "管理组", null, null, null, [WorkspacePermission.RemoveMembers]).Role;
|
||||
var movers = env.Service.CreateWorkspaceRoleResult(ch.Id, owner, "结构组", null, null, null, [WorkspacePermission.ManageStructure]).Role;
|
||||
env.Service.SetWorkspaceRoleMembers(ch.Id, owner, admins.Id, ["alice", "bob"]);
|
||||
env.Service.SetWorkspaceRoleMembers(ch.Id, owner, movers.Id, ["carol"]);
|
||||
|
||||
// 所有者:全部能力
|
||||
var asOwner = env.Service.GetChannel(ch.Id, owner);
|
||||
Assert.True(asOwner.IsOwner); Assert.True(asOwner.CanManageStructure); Assert.True(asOwner.CanDeleteAny); Assert.True(asOwner.CanDeleteOwn);
|
||||
Assert.True(asOwner.CanCreateChannel && asOwner.CanManageFolders && asOwner.CanInvite && asOwner.CanEditSettings && asOwner.CanRemoveMembers);
|
||||
|
||||
// 身份组授予的权限:被授予「管理频道和分组」的不是所有者,却能管理结构;其他所有者专属的操作它仍然做不了
|
||||
var asCarol = env.Service.GetChannel(ch.Id, carol);
|
||||
Assert.False(asCarol.IsOwner); Assert.True(asCarol.CanManageStructure); Assert.False(asCarol.CanRemoveMembers);
|
||||
Assert.False(asCarol.CanCreateChannel || asCarol.CanManageFolders || asCarol.CanInvite || asCarol.CanEditSettings);
|
||||
var asAlice = env.Service.GetChannel(ch.Id, alice);
|
||||
Assert.False(asAlice.IsOwner); Assert.False(asAlice.CanManageStructure); Assert.True(asAlice.CanRemoveMembers);
|
||||
// 默认删除策略:成员只能删自己的
|
||||
Assert.False(asAlice.CanDeleteAny); Assert.True(asAlice.CanDeleteOwn);
|
||||
|
||||
// 谁能移除谁:所有者能移除除自己外的人;被授权的不能移除自己、所有者、同样被授权的人
|
||||
var byOwner = env.Service.MembersView(ch.Id, owner).ToDictionary(v => v.Member.Username, v => v.CanRemove);
|
||||
Assert.True(byOwner["alice"]); Assert.True(byOwner["carol"]);
|
||||
Assert.False(byOwner[env.Service.GetChannel(ch.Id, owner).CreatorAccount!]);
|
||||
var byAlice = env.Service.MembersView(ch.Id, alice).ToDictionary(v => v.Member.Username, v => v.CanRemove);
|
||||
Assert.False(byAlice["alice"]); Assert.False(byAlice["bob"]); Assert.True(byAlice["carol"]);
|
||||
Assert.All(env.Service.MembersView(ch.Id, carol), view => Assert.False(view.CanRemove)); // 没有移除权限
|
||||
// 展示和执行用同一条规则:展示说不能移除的,真去移除也会被拒绝
|
||||
Assert.Throws<WebDropException>(() => env.Service.RevokeInvitation(ch.Id, alice, "bob"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user