diff --git a/Server/Common/Dir.cs b/Server/Common/Dir.cs index 73cbe95..db7080c 100644 --- a/Server/Common/Dir.cs +++ b/Server/Common/Dir.cs @@ -127,7 +127,7 @@ public class Dir(string path, List? children = null, NextOpType? nex /// 是否更新文件目录树 /// public void Combine( - FileDirOp? fileDirOp, + FileDirOpStra? fileDirOp, Dir diffdir, bool IsUpdateObject = true, bool IsUpdateDirFile = false @@ -257,7 +257,7 @@ public class Dir(string path, List? children = null, NextOpType? nex /// /// 它的一个clone将被合并的dir,它的NextOp 不应该是空,否则什么都不会发生 /// - public void CombineJustDirFile(FileDirOp fileDirOp, Dir diffDir) + public void CombineJustDirFile(FileDirOpStra fileDirOp, Dir diffDir) { Combine(fileDirOp, diffDir, false, true); } @@ -332,9 +332,9 @@ public class Dir(string path, List? children = null, NextOpType? nex /// 文件的修改时间,是否修改文件的修改时间,需要定义文件的写入策略 WriteFileStrageFunc /// /// - public void WriteByThisInfo(FileDirOp fileDirOp) + public void WriteByThisInfo(FileDirOpStra fileDirOp) { - static void f(Dir dir, FileDirOp fileDirOp) + static void f(Dir dir, FileDirOpStra fileDirOp) { foreach (var child in dir.Children) { @@ -363,7 +363,94 @@ public class Dir(string path, List? children = null, NextOpType? nex } /// - /// 比较两个目录文件树是否相同,不相同返回差异部分,左侧是右侧的下一个版本 + /// 校验文件夹和文件权限 + /// + public void AccessCheck() + { + this.Children.ForEach(e => + { + if (e is File file) + { + if (file.NextOp == null) { } + else if (file.NextOp == NextOpType.Add) + { + if ( + !AccessWrapper.CheckDirAccess( + Path.GetDirectoryName(file.FormatedPath) + ?? throw new DirectoryNotFoundException( + $"{file.FormatedPath} 此父路径不存在" + ), + [DirAcess.CreateFiles] + ) + ) + { + throw new UnauthorizedAccessException($"{file.FormatedPath} 无权限创建文件"); + } + } + else if (file.NextOp == NextOpType.Modify) + { + if ( + !( + AccessWrapper.CheckFileAccess(file.FormatedPath, [FileAccess.Delete]) + && AccessWrapper.CheckDirAccess( + Path.GetDirectoryName(file.FormatedPath) + ?? throw new DirectoryNotFoundException( + $"{file.FormatedPath} 此父路径不存在" + ), + [DirAcess.CreateFiles] + ) + ) + ) + { + throw new UnauthorizedAccessException( + $"{file.FormatedPath} 无权限删除源文件或者创建新文件" + ); + } + } + else if (file.NextOp == NextOpType.Del) + { + if (!AccessWrapper.CheckFileAccess(file.FormatedPath, [FileAccess.Delete])) + { + throw new UnauthorizedAccessException($"{file.FormatedPath} 无权限删除源文件"); + } + } + } + else if (e is Dir dir) + { + if (dir.NextOp == null) { } + else if (dir.NextOp == NextOpType.Add) + { + if ( + !AccessWrapper.CheckDirAccess( + Path.GetDirectoryName(dir.FormatedPath) + ?? throw new DirectoryNotFoundException( + $"{dir.FormatedPath} 此父路径不存在" + ), + [DirAcess.CreateDirectories, DirAcess.CreateFiles] + ) + ) + { + throw new UnauthorizedAccessException($"{dir.FormatedPath} 无权限创建文件夹或者文件"); + } + } + else if (dir.NextOp == NextOpType.Del) + { + if (!AccessWrapper.CheckDirAccess(dir.FormatedPath, [DirAcess.Delete])) + { + throw new UnauthorizedAccessException($"{dir.FormatedPath} 无权限删除文件夹"); + } else { + //校验是否拥有子文件或者文件夹的删除权限, + dir.AccessCheck(); + } + } + } + }); + } + + /// + /// 比较两个目录文件树是否相同,不相同返回差异部分,左侧是右侧的下一个版本,任何一个节点的nextop != null,即所有 + /// 节点都会打上标记 + /// 文件夹的 NextOp 只有新增和删除 /// /// /// diff --git a/Server/Common/FileDirOp.cs b/Server/Common/FileDirOp.cs index da89ce5..d0ed3da 100644 --- a/Server/Common/FileDirOp.cs +++ b/Server/Common/FileDirOp.cs @@ -1,8 +1,16 @@ -using System.Text; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; +using Microsoft.VisualBasic; namespace Common; -public abstract class FileDirOp +/// +/// 文件操作策略 +/// +public abstract class FileDirOpStra { public abstract void FileCreate(string absolutePath, DateTime mtime); @@ -13,73 +21,571 @@ public abstract class FileDirOp public abstract void DirDel(Dir dir, bool IsRecursion = true); } -public class SimpleFileDirOpForTest : FileDirOp +/// +/// 文件目录打包 +/// +/// +public class FileDirOpForPack(string srcRootPath, string dstRootPath) : FileDirOpStra { + /// + /// 目标根目录 + /// + public readonly string DstRootPath = dstRootPath; + + /// + /// 源目录 + /// + public readonly string SrcRootPath = srcRootPath; + + /// + /// 最终完成时的压缩 + /// + public void FinallyCompress() + { + var x = DstRootPath; + } + public override void FileCreate(string absolutePath, DateTime mtime) { - using (FileStream fs = System.IO.File.OpenWrite(absolutePath)) - { - byte[] info = Encoding.UTF8.GetBytes($"this is {absolutePath},Now{mtime}"); - fs.Write(info, 0, info.Length); - } - System.IO.File.SetLastWriteTime(absolutePath, mtime); - } - - public override void FileModify(string absolutePath, DateTime mtime) - { - using (FileStream fs = System.IO.File.OpenWrite(absolutePath)) - { - byte[] info = Encoding.UTF8.GetBytes($"this is {absolutePath},Now{mtime}"); - fs.Write(info, 0, info.Length); - } - System.IO.File.SetLastWriteTime(absolutePath, mtime); - } - - public override void FileDel(string absolutePath) - { - //ToDo 权限检查 - if (System.IO.File.Exists(absolutePath)) - { - System.IO.File.Delete(absolutePath); - } + throw new NotImplementedException(); } public override void DirCreate(Dir dir, bool IsRecursion = true) { - //TODO需做权限检查 - if (!Directory.Exists(dir.FormatedPath)) - { - Directory.CreateDirectory(dir.FormatedPath); - if (IsRecursion) - { - foreach (var fd in dir.Children) - { - if (fd is File file) - { - this.FileCreate(file.FormatedPath, file.MTime); - } - else if (fd is Dir sdir) - { - DirCreate(sdir); - } - } - } - } + throw new NotImplementedException(); + } + + public override void FileModify(string absolutePath, DateTime mtime) + { + throw new NotImplementedException(); + } + + public override void FileDel(string absolutePath) + { + throw new NotImplementedException(); } public override void DirDel(Dir dir, bool IsRecursion = true) { - //TODO 权限检查 正式徐执行递归 - if (!IsRecursion) + throw new NotImplementedException(); + } +} + +public class FileDirOpForUnpack(string srcCompressedPath, string dstRootPath) : FileDirOpStra +{ + /// + /// 解压缩,必须首先调用 + /// + public void FirstUnComparess() + { + var x = SrcCompressedPath; + } + + /// + /// 目标根目录 + /// + public readonly string DstRootPath = dstRootPath; + + /// + /// 源目录 + /// + public readonly string SrcCompressedPath = srcCompressedPath; + + /// + /// 最终完成时的压缩 + /// + public override void FileCreate(string absolutePath, DateTime mtime) + { + throw new NotImplementedException(); + } + + public override void DirCreate(Dir dir, bool IsRecursion = true) + { + throw new NotImplementedException(); + } + + public override void FileModify(string absolutePath, DateTime mtime) + { + throw new NotImplementedException(); + } + + public override void FileDel(string absolutePath) + { + throw new NotImplementedException(); + } + + public override void DirDel(Dir dir, bool IsRecursion = true) + { + throw new NotImplementedException(); + } +} + +/// +/// 文件目录权限校验 +/// +public class FileDirOpForAccessCheck : FileDirOpStra +{ + public override void FileCreate(string absolutePath, DateTime mtime) + { + throw new NotImplementedException(); + } + + public override void DirCreate(Dir dir, bool IsRecursion = true) + { + throw new NotImplementedException(); + } + + public override void FileModify(string absolutePath, DateTime mtime) + { + throw new NotImplementedException(); + } + + public override void FileDel(string absolutePath) + { + throw new NotImplementedException(); + } + + public override void DirDel(Dir dir, bool IsRecursion = true) + { + throw new NotImplementedException(); + } +} + +public enum FileAccess +{ + Read, + Write, + Delete, + Execute +} + +public enum DirAcess +{ + /// + /// 读取权限 + /// + Read, + + /// + /// 写入权限 + /// + Write, + + /// + /// 修改权限 + /// + Modify, + + /// + /// 列出文件夹权限 + /// + ListDirectory, + + /// + /// 创建文件权限 + /// + CreateFiles, + + /// + /// 创建文件夹权限 + /// + CreateDirectories, + + /// + /// 删除文件权限 + /// + Delete, + + /// + /// 删除文件夹及其子文件 + /// + DeleteSubdirectoriesAndFiles, +} + +/// +/// 运行此软件的用户与目标软件的用户最好是 一个用户,一个用户组,或者运行此软件的用户具备最高权限。 +/// +public class AccessWrapper +{ + /// + /// + /// + /// + public static void FreeThisDirAccess(string absolutePath) + { + if ( + CheckDirAccess( + absolutePath, + [ + DirAcess.Read, + DirAcess.Write, + DirAcess.Modify, + DirAcess.Delete, + DirAcess.ListDirectory, + DirAcess.CreateFiles, + DirAcess.CreateDirectories, + DirAcess.DeleteSubdirectoriesAndFiles + ] + ) + ) { } + else { - if (Directory.Exists(dir.FormatedPath)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Directory.Delete(dir.FormatedPath, true); + DirectoryInfo dirInfo = new(absolutePath); + //获得该文件的访问权限 + var dirSecurity = dirInfo.GetAccessControl(); + //设定文件ACL继承 + InheritanceFlags inherits = + InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit; + + var cUser = + WindowsIdentity.GetCurrent().User + ?? throw new Exception("GetWindowsIdentity failed. 你需要手动处理发布内容!"); + FileSystemAccessRule ReadRule = + new( + cUser, + FileSystemRights.Read, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule WriteRule = + new( + cUser, + FileSystemRights.Write, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule ModifyRule = + new( + cUser, + FileSystemRights.Modify, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule DeleteRule = + new( + cUser, + FileSystemRights.Delete, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule ListDirectoryRule = + new( + cUser, + FileSystemRights.ListDirectory, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule CreateFilesRule = + new( + cUser, + FileSystemRights.CreateFiles, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule CreateDirsRule = + new( + cUser, + FileSystemRights.CreateDirectories, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + FileSystemAccessRule DeleteSubdirectoriesAndFilesRule = + new( + cUser, + FileSystemRights.DeleteSubdirectoriesAndFiles, + inherits, + PropagationFlags.None, + AccessControlType.Allow + ); + if ( + dirSecurity.ModifyAccessRule(AccessControlModification.Add, ReadRule, out _) + && dirSecurity.ModifyAccessRule(AccessControlModification.Add, WriteRule, out _) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + ModifyRule, + out _ + ) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + ListDirectoryRule, + out _ + ) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + CreateFilesRule, + out _ + ) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + CreateDirsRule, + out _ + ) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + DeleteRule, + out _ + ) + && dirSecurity.ModifyAccessRule( + AccessControlModification.Add, + DeleteSubdirectoriesAndFilesRule, + out _ + ) + ) { } + else + { + throw new Exception("AddAccessRule failed. 你需要手动处理发布内容!"); + } + //设置访问权限 + dirInfo.SetAccessControl(dirSecurity); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + //TODO Linux文件权限 + } + else + { + throw new NotSupportedException( + $"{RuntimeInformation.OSDescription} is not supported." + ); + } + } + } + + public static void FreeThisFileAccess(string absolutePath) + { + if ( + CheckFileAccess( + absolutePath, + [FileAccess.Read, FileAccess.Write, FileAccess.Delete, FileAccess.Execute] + ) + ) { } + else + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + FileInfo fileInfo = new(absolutePath); + //获得该文件的访问权限 + FileSecurity fileSecurity = fileInfo.GetAccessControl(); + + var cUser = + WindowsIdentity.GetCurrent().User + ?? throw new Exception("GetWindowsIdentity failed. 你需要手动处理发布内容!"); + FileSystemAccessRule ReadRule = + new(cUser, FileSystemRights.Read, AccessControlType.Allow); + FileSystemAccessRule WriteRule = + new(cUser, FileSystemRights.Write, AccessControlType.Allow); + FileSystemAccessRule DeleteRule = + new(cUser, FileSystemRights.Delete, AccessControlType.Allow); + FileSystemAccessRule ExecuteRule = + new(cUser, FileSystemRights.ExecuteFile, AccessControlType.Allow); + if ( + fileSecurity.ModifyAccessRule(AccessControlModification.Add, ReadRule, out _) + && fileSecurity.ModifyAccessRule( + AccessControlModification.Add, + WriteRule, + out _ + ) + && fileSecurity.ModifyAccessRule( + AccessControlModification.Add, + DeleteRule, + out _ + ) + && fileSecurity.ModifyAccessRule( + AccessControlModification.Add, + ExecuteRule, + out _ + ) + ) { } + else + { + throw new Exception("AddAccessRule failed. 你需要手动处理发布内容!"); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + //TODO Linux文件权限 + } + else + { + throw new NotSupportedException( + $"{RuntimeInformation.OSDescription} is not supported." + ); + } + } + } + + public static bool CheckDirAccess(string absolutePath, DirAcess[] access) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + DirectoryInfo dirInfo = new(absolutePath); + //获得该文件的访问权限 + var dirSecurity = dirInfo.GetAccessControl(); + var ac = dirSecurity + .GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)) + .Cast(); +#pragma warning disable CA1416 // Validate platform compatibility + var it = + from i in ac + where i.IdentityReference == WindowsIdentity.GetCurrent().User + select i; +#pragma warning restore CA1416 // Validate platform compatibility + List caccess = []; + foreach (var i in it) + { + if (i.FileSystemRights == FileSystemRights.FullControl) + { + return true; + } + else if (i.FileSystemRights == FileSystemRights.Read) + { + caccess.Add(DirAcess.Read); + } + else if (i.FileSystemRights == FileSystemRights.Write) + { + caccess.Add(DirAcess.Write); + } + else if (i.FileSystemRights == FileSystemRights.Delete) + { + caccess.Add(DirAcess.Delete); + } + else if (i.FileSystemRights == FileSystemRights.Modify) + { + caccess.Add(DirAcess.Modify); + } + else if (i.FileSystemRights == FileSystemRights.ListDirectory) + { + caccess.Add(DirAcess.ListDirectory); + } + else if (i.FileSystemRights == FileSystemRights.CreateFiles) + { + caccess.Add(DirAcess.CreateFiles); + } + else if (i.FileSystemRights == FileSystemRights.CreateDirectories) + { + caccess.Add(DirAcess.CreateDirectories); + } + else if (i.FileSystemRights == FileSystemRights.DeleteSubdirectoriesAndFiles) + { + caccess.Add(DirAcess.DeleteSubdirectoriesAndFiles); + } + } + foreach (var i in access) + { + if (!caccess.Contains(i)) + { + return false; + } + } + return true; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + //TODO Linux文件权限 + return true; } else { - throw new NotImplementedException(); + throw new NotSupportedException( + $"{RuntimeInformation.OSDescription} is not supported." + ); + } + } + + public static bool CheckFileAccess(string absolutePath, FileAccess[] access) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + FileInfo fileInfo = new(absolutePath); + //获得该文件的访问权限 + FileSecurity fileSecurity = fileInfo.GetAccessControl(); + var ac = fileSecurity + .GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)) + .Cast(); +#pragma warning disable CA1416 // Validate platform compatibility + var it = + from i in ac + where i.IdentityReference == WindowsIdentity.GetCurrent().User + select i; +#pragma warning restore CA1416 // Validate platform compatibility + + + List caccess = []; + foreach (var i in it) + { + if (i.FileSystemRights == FileSystemRights.FullControl) + { + return true; + } + else if (i.FileSystemRights == FileSystemRights.Read) + { + caccess.Add(FileAccess.Read); + } + else if (i.FileSystemRights == FileSystemRights.Write) + { + caccess.Add(FileAccess.Write); + } + else if (i.FileSystemRights == FileSystemRights.Delete) + { + caccess.Add(FileAccess.Delete); + } + else if (i.FileSystemRights == FileSystemRights.ExecuteFile) + { + caccess.Add(FileAccess.Execute); + } + } + foreach (var i in access) + { + if (!caccess.Contains(i)) + { + return false; + } + } + return true; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + //TODO Linux文件夹权限 + return true; + } + else + { + throw new NotSupportedException( + $"{RuntimeInformation.OSDescription} is not supported." + ); + } + } + + /// + /// 获取当前用户 + /// + /// + /// + + public static string GetCurrentUser() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return System.Security.Principal.WindowsIdentity.GetCurrent().Name; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Environment.UserName; + } + else + { + throw new NotSupportedException( + $"{RuntimeInformation.OSDescription} is not supported." + ); } } } diff --git a/Server/ServerTest/FileDirOpForTest.cs b/Server/ServerTest/FileDirOpForTest.cs new file mode 100644 index 0000000..668d829 --- /dev/null +++ b/Server/ServerTest/FileDirOpForTest.cs @@ -0,0 +1,75 @@ +using System.Text; +using Common; + +namespace ServerTest; + +/// +/// 简单文件操作 +/// +public class SimpleFileDirOp : FileDirOpStra +{ + public override void FileCreate(string absolutePath, DateTime mtime) + { + using (FileStream fs = System.IO.File.OpenWrite(absolutePath)) + { + byte[] info = Encoding.UTF8.GetBytes($"this is {absolutePath},Now{mtime}"); + fs.Write(info, 0, info.Length); + } + System.IO.File.SetLastWriteTime(absolutePath, mtime); + } + + public override void FileModify(string absolutePath, DateTime mtime) + { + using (FileStream fs = System.IO.File.OpenWrite(absolutePath)) + { + byte[] info = Encoding.UTF8.GetBytes($"this is {absolutePath},Now{mtime}"); + fs.Write(info, 0, info.Length); + } + System.IO.File.SetLastWriteTime(absolutePath, mtime); + } + + public override void FileDel(string absolutePath) + { + if (System.IO.File.Exists(absolutePath)) + { + System.IO.File.Delete(absolutePath); + } + } + + public override void DirCreate(Dir dir, bool IsRecursion = true) + { + if (!Directory.Exists(dir.FormatedPath)) + { + Directory.CreateDirectory(dir.FormatedPath); + if (IsRecursion) + { + foreach (var fd in dir.Children) + { + if (fd is Common.File file) + { + this.FileCreate(file.FormatedPath, file.MTime); + } + else if (fd is Dir sdir) + { + DirCreate(sdir); + } + } + } + } + } + + public override void DirDel(Dir dir, bool IsRecursion = true) + { + if (!IsRecursion) + { + if (Directory.Exists(dir.FormatedPath)) + { + Directory.Delete(dir.FormatedPath, true); + } + } + else + { + throw new NotImplementedException(); + } + } +} diff --git a/Server/ServerTest/FilesSeed.cs b/Server/ServerTest/FilesSeed.cs index 15ccda1..2721a97 100644 --- a/Server/ServerTest/FilesSeed.cs +++ b/Server/ServerTest/FilesSeed.cs @@ -173,14 +173,14 @@ public class FilesSeed : IDisposable ), ] ); - fileDirOp = new SimpleFileDirOpForTest(); + fileDirOp = new SimpleFileDirOp(); } private readonly string TestPath = Path.Combine(Directory.GetCurrentDirectory(), "../../.."); public Dir NewDir; public Dir OldDir; public Dir DiffDir; - public FileDirOp fileDirOp; + public FileDirOpStra fileDirOp; public void Dispose() {