Files
justme 17638b6e20 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
2026-10-19 13:38:40 +08:00

49 lines
2.5 KiB
C#

using System.Text.RegularExpressions;
namespace WebDrop.Core.Tests;
/// <summary>
/// 权限规则只写在 ChannelPolicy 一处:别的地方只许调用,不许再各写一遍。
/// 这个测试扫描源码,发现在 ChannelPolicy 之外重新判断角色、读写授权就失败,防止重复实现长回来。
/// </summary>
public sealed class PolicySingleSourceTests
{
private static string SourceRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !Directory.Exists(Path.Combine(dir.FullName, "src", "WebDrop.Core"))) dir = dir.Parent;
return Path.Combine(dir?.FullName ?? throw new InvalidOperationException("找不到 server/src"), "src");
}
// 这些写法只能出现在 ChannelPolicy.cs(数据类的定义和令牌校验除外)
private static readonly (string Name, Regex Pattern)[] Forbidden =
[
("直接读频道访问授权的读写位", new Regex(@"\.(ChatRead|ChatWrite|FilesRead|FilesUpload)\b")),
("直接比较是不是所有者", new Regex(@"Role\s*[!=]=\s*Role\.Owner")),
("自己拼「所有者或创建者」", new Regex(@"AccountName\s*==\s*\w+\.CreatorAccount")),
];
// 令牌校验(凭据层,不是授权规则)和数据定义,允许出现
private static readonly string[] Allowed =
["ChannelPolicy.cs", "WorkspaceCrypto.cs", "RequestAuth.cs", "ShareService.cs"];
[Fact]
public void AuthorizationRulesAreWrittenOnlyInChannelPolicy()
{
var offenders = new List<string>();
foreach (var file in Directory.EnumerateFiles(SourceRoot(), "*.cs", SearchOption.AllDirectories))
{
if (file.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar) || file.Contains(Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar)) continue;
if (Allowed.Contains(Path.GetFileName(file))) continue;
var lines = File.ReadAllLines(file);
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].TrimStart().StartsWith("//") || lines[i].TrimStart().StartsWith("///")) continue;
foreach (var (name, pattern) in Forbidden)
if (pattern.IsMatch(lines[i])) offenders.Add($"{Path.GetFileName(file)}:{i + 1} {name}: {lines[i].Trim()}");
}
}
Assert.True(offenders.Count == 0, "这些地方在 ChannelPolicy 之外重新判断了权限,请改成调用 ChannelPolicy:\n" + string.Join("\n", offenders));
}
}