foxygit / digitalskrivare_merge Log in
commits tags

/src/Remiss.Core/Security/SecureTempStore.cs · 2.66 KB

raw
using System.Security.AccessControl;
using System.Security.Principal;

namespace Remiss.Core.Security;

/// <summary>
/// En jobb-isolerad temp-mapp för PDF:er som innehåller patientdata.
/// <list type="bullet">
///   <item>Ligger under LOCALAPPDATA (användarprofil), inte %TEMP%.</item>
///   <item>ACL låses till nuvarande användare + SYSTEM + Administrators.</item>
///   <item><see cref="Dispose"/> skriver över och raderar allt – körs även vid fel.</item>
/// </list>
/// </summary>
public sealed class SecureTempStore : IDisposable
{
    public string Root { get; }

    public SecureTempStore(string baseDir)
    {
        Directory.CreateDirectory(baseDir);
        Root = Path.Combine(baseDir, "job-" + Guid.NewGuid().ToString("N"));
        var dir = Directory.CreateDirectory(Root);
        TryRestrictAccess(dir);
    }

    public string NewFile(string name) => Path.Combine(Root, name);

    [System.Runtime.Versioning.SupportedOSPlatform("windows")]
    private static void TryRestrictAccess(DirectoryInfo dir)
    {
        try
        {
            var security = new DirectorySecurity();
            security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);

            const InheritanceFlags inherit = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;

            var me = WindowsIdentity.GetCurrent().User!;
            security.AddAccessRule(new FileSystemAccessRule(
                me, FileSystemRights.FullControl, inherit, PropagationFlags.None, AccessControlType.Allow));

            var admins = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
            security.AddAccessRule(new FileSystemAccessRule(
                admins, FileSystemRights.FullControl, inherit, PropagationFlags.None, AccessControlType.Allow));

            var system = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
            security.AddAccessRule(new FileSystemAccessRule(
                system, FileSystemRights.FullControl, inherit, PropagationFlags.None, AccessControlType.Allow));

            dir.SetAccessControl(security);
        }
        catch
        {
            // Bäst-möjligt: profilmappen är redan användar-scopad även utan detta.
        }
    }

    public void Dispose()
    {
        try
        {
            if (!Directory.Exists(Root))
                return;

            foreach (var file in Directory.EnumerateFiles(Root, "*", SearchOption.AllDirectories))
                SecureFile.Shred(file);

            Directory.Delete(Root, recursive: true);
        }
        catch
        {
            // ignore – nästa körning städar kvarvarande job-* mappar vid behov
        }
    }
}