From 7f64b7f37265944e1405d3ba49c3989563521237 Mon Sep 17 00:00:00 2001 From: dahall Date: Thu, 23 Apr 2026 19:40:44 -0600 Subject: [PATCH 1/2] Updated Vanara package to 5.0.5 --- .../client/DpiAwarenessContextRes.dll | Bin 3584 -> 3584 bytes Directory.Packages.props | 2 +- Sampler/CodeIndexBuilder.cs | 32 ++++++++++++++++++ Sampler/Form1.cs | 14 ++++---- Sampler/NuGetPackages.Designer.cs | 1 - Sampler/NuGetPackages.cs | 8 ++--- Sampler/Program.cs | 3 +- .../security/authorization/aclapi/aclapi.cs | 4 +-- .../DragDropVisuals/DragDropVisualsRes.dll | Bin 2048 -> 2048 bytes .../ProgressSinkSampleAppRes.dll | Bin 2048 -> 2048 bytes .../NamespaceTreeSDKSampleRes.dll | Bin 2560 -> 2560 bytes .../ShellLibraryBackupRes.dll | Bin 2560 -> 2560 bytes .../SimpleDictation/SimpleDictationRes.dll | Bin 7680 -> 7680 bytes .../SimpleTelephony/SimpleTelephonyRes.dll | Bin 7680 -> 7680 bytes .../TtsApplication/TtsApplicationRes.dll | Bin 743936 -> 743936 bytes .../winui/speech/talkback/TalkBackRes.dll | Bin 7168 -> 7168 bytes WinClassicSamplesCS.sln | 1 + 17 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 Sampler/CodeIndexBuilder.cs diff --git a/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll b/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll index a389d6836b2c6fb3a44473090199a15de0cf5ee1..e924394d43db826f787c431002b167240a675dde 100644 GIT binary patch delta 23 ccmZpWX^@$)gZV<{tBt!o8A0@BcBY@)0DJceLjV8( delta 23 ccmZpWX^@$)gPBkB#>U;Aj39b5JJU~Y0Ak+=8vptrue true $(NoWarn);NU1507 - 5.0.4 + 5.0.5 diff --git a/Sampler/CodeIndexBuilder.cs b/Sampler/CodeIndexBuilder.cs new file mode 100644 index 00000000..059ceed4 --- /dev/null +++ b/Sampler/CodeIndexBuilder.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Sampler; + +/// +/// Given a list of assemblies, this class will build an index of all the public types and members in those assemblies, so that we can +/// quickly look up information about them later. +/// +internal static class CodeIndexBuilder +{ + //public static CodeIndex BuildIndex(IEnumerable assemblies) + //{ + // var index = new CodeIndex(); + // foreach (var assembly in assemblies) + // { + // foreach (var type in assembly.GetExportedTypes()) + // { + // index.Types[type.FullName] = type; + // foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)) + // { + // index.Members[$"{type.FullName}.{member.Name}"] = member; + // } + // } + // } + // return index; + //} +} diff --git a/Sampler/Form1.cs b/Sampler/Form1.cs index 3e21be75..143255cd 100644 --- a/Sampler/Form1.cs +++ b/Sampler/Form1.cs @@ -15,7 +15,7 @@ public partial class Form1 : Form private const string folderKey = "5EEB255733234c4dBECF9A128E896A1E"; private const char sep = '\\'; - private DirectoryInfo[] topDirs; + private DirectoryInfo[] topDirs = []; public Form1() => InitializeComponent(); @@ -56,9 +56,9 @@ private static TreeNode AddSystemNode(TreeNodeCollection parent, string systemIt try { if (ilkey == folderKey) - imageList.Images.Add(ilkey, GetSystemIcon()); + imageList.Images.Add(ilkey, GetSystemIcon()!); else - imageList.Images.Add(ilkey, IconExtension.GetFileIcon(ext, IconSize.Small).ToBitmap()); + imageList.Images.Add(ilkey, IconExtension.GetFileIcon(ext, IconSize.Small)!.ToBitmap()); } catch (ArgumentException ex) { @@ -68,11 +68,11 @@ private static TreeNode AddSystemNode(TreeNodeCollection parent, string systemIt return parent.Add(systemItemPath, Path.GetFileName(systemItemPath), ilkey, ilkey); } - private static Bitmap GetSystemIcon() + private static Bitmap? GetSystemIcon() { var shfi = new SHFILEINFO(); HIMAGELIST hSystemImageList = SHGetFileInfo("", 0, ref shfi, SHFILEINFO.Size, SHGFI.SHGFI_SYSICONINDEX | SHGFI.SHGFI_SMALLICON); - return hSystemImageList.IsNull ? null : ImageList_GetIcon(hSystemImageList, shfi.iIcon, IMAGELISTDRAWFLAGS.ILD_TRANSPARENT).ToBitmap(); + return hSystemImageList.IsNull ? null : ImageList_GetIcon(hSystemImageList, shfi.iIcon, IMAGELISTDRAWFLAGS.ILD_TRANSPARENT)?.ToBitmap(); } private void explorerBrowser_SelectionChanged(object sender, EventArgs e) @@ -94,14 +94,14 @@ private void LoadTree() root.Expand(); foreach (var fi in RootPath.EnumerateFiles("*.csproj", SearchOption.AllDirectories)) { - if (!fi.DirectoryName.EndsWith("\\Sampler")) + if (!fi.DirectoryName!.EndsWith("\\Sampler")) AddLeaf(root, fi, projectView.ImageList); } } private void projectView_AfterSelect(object sender, TreeViewEventArgs e) { - var di = new DirectoryInfo(e.Node.Name); + var di = new DirectoryInfo(e.Node!.Name); var prj = di.Exists ? di.EnumerateFiles("*.csproj").FirstOrDefault() : null; explorerBrowser.Navigate(new ShellFolder(e.Node.Name)); if (prj != null) diff --git a/Sampler/NuGetPackages.Designer.cs b/Sampler/NuGetPackages.Designer.cs index 7cdf5442..a9ff66ad 100644 --- a/Sampler/NuGetPackages.Designer.cs +++ b/Sampler/NuGetPackages.Designer.cs @@ -119,7 +119,6 @@ private void InitializeComponent() FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; Name = "NuGetPackages"; Text = "NuGetPackages"; - Load += NuGetPackages_Load; splitContainer1.Panel1.ResumeLayout(false); splitContainer1.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); diff --git a/Sampler/NuGetPackages.cs b/Sampler/NuGetPackages.cs index 93b82efc..db2f7590 100644 --- a/Sampler/NuGetPackages.cs +++ b/Sampler/NuGetPackages.cs @@ -19,13 +19,11 @@ public partial class NuGetPackages : Form static readonly ILogger logger = NullLogger.Instance; // TODO: Replace with actual logger if needed static readonly CancellationToken cancellationToken = CancellationToken.None; // TODO: Replace with actual cancellation token if needed - public NuGetPackages() - { - InitializeComponent(); - } + public NuGetPackages() => InitializeComponent(); - private void NuGetPackages_Load(object sender, EventArgs e) + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); listBox1.Format += (s, args) => args.Value = args.ListItem is IPackageSearchMetadata r ? r.Title : args.ListItem?.ToString() ?? string.Empty; Task.Factory.StartNew(async () => { diff --git a/Sampler/Program.cs b/Sampler/Program.cs index fa7038b7..ab546a09 100644 --- a/Sampler/Program.cs +++ b/Sampler/Program.cs @@ -1,5 +1,4 @@ -using System.Runtime.Versioning; -using System.Windows.Forms; +using System.Windows.Forms; namespace Sampler; diff --git a/Win7Samples/security/authorization/aclapi/aclapi.cs b/Win7Samples/security/authorization/aclapi/aclapi.cs index 0ed7a15e..bfea6268 100644 --- a/Win7Samples/security/authorization/aclapi/aclapi.cs +++ b/Win7Samples/security/authorization/aclapi/aclapi.cs @@ -49,10 +49,10 @@ private static int Main(string[] args) BuildExplicitAccessWithName(out var explicitaccess, TrusteeName, AccessMask, option, InheritFlag); // add specified access to the object - SetEntriesInAcl(1, [explicitaccess], ExistingDacl, out var NewAcl).ThrowIfFailed(); + SetEntriesInAcl([explicitaccess], ExistingDacl, out var NewAcl).ThrowIfFailed(); // apply new security to file - SetNamedSecurityInfo(FileName, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, default, default, NewAcl, default).ThrowIfFailed(); + SetNamedSecurityInfo(FileName, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, ppDacl: NewAcl).ThrowIfFailed(); return 0; } diff --git a/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll b/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll index a9dec09854b12f74c08ff829055f0234430b5ec8..d3eb6a78b6a8d55b04cf38b20b5c83340d372b3c 100644 GIT binary patch delta 23 ccmZn=Xb_mNgL!4U;Aj39b5JJU~A0Ac|M6#xJL diff --git a/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll b/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll index 44fd469dcca78734314f55927a94e94a91ea579f..69eb0d10e6eec1e9ab39f6de16cd89a490ed2b0c 100644 GIT binary patch delta 23 ccmZn=Xb_mNgLzfvtBt!o8A0@BcBY@K0Cs5!*#H0l delta 23 ccmZn=Xb_mNgIQAZ#>U;Aj39b5JJU~A0Ac9}6951J diff --git a/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll b/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll index aee02852a5657f432434e4c54144b5b48632e812..95e1c99f96cfa1b12f0269950856d97b557c85f2 100644 GIT binary patch delta 23 ccmZn=X%LyPgL!G@tBt!o8A0@BcBY@~0Ct-R*#H0l delta 23 ccmZn=X%LyPgIP-R#>U;Aj39b5JJU~g0Ai~M9{>OV diff --git a/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll b/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll index d8f98ef0fd503a708ecf6865419a4c188cf6506c..37792f7a94a478cf0988f73a684c01fc3e1615f1 100644 GIT binary patch delta 23 ccmZn=X%LyPgL!l2tBt!o8A0@BcBY@~0C(#N^#A|> delta 23 ccmZn=X%LyPgIQAZ#>U;Aj39b5JJU~g0AiB}9RL6T diff --git a/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll b/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll index 86a7fe0aa62d05aab7f57369febf7a7fbbf52c32..84321dbc5e7c7b2f91676c28326949fb1d26b82d 100644 GIT binary patch delta 23 ccmZp$X|S2FgLz@*tBt!o8A0@BcBY@w0DRvHH2?qr delta 23 ccmZp$X|S2FgZZb%jg7lK8A0@BcBY@w0Dd6~KL7v# diff --git a/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll b/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll index ca2475e9064d99fed0e4e8d6d7bcde34c9e26bee..b59fe2925100389bc221fbf55610853bf7bf11f0 100644 GIT binary patch delta 23 ccmZp$X|S2FgLy&btBt!o8A0@BcBY@w0DQ*^GXMYp delta 23 ccmZp$X|S2FgZZb%jg7lK8A0@BcBY@w0Dd6~KL7v# diff --git a/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll b/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll index 2f26d6d9635623a0049d126a64f358fc012fb2f8..d49f4adae7027637c5bd0aa61c1a3096f48c3402 100644 GIT binary patch delta 69 zcmZqJqT8@Vcft004FpApigX delta 69 zcmZqJqT8@Vcft00s~eHvj+t diff --git a/Win7Samples/winui/speech/talkback/TalkBackRes.dll b/Win7Samples/winui/speech/talkback/TalkBackRes.dll index deed6aea71ed3e9d86b5c174c678a9f0d02ed218..62ba3b48f0e73481218fedafbed5d262c377caec 100644 GIT binary patch delta 23 ccmZp$Xt0>DgL!u5tBt!o8A0@BcBY?_0DGkh9{>OV delta 23 ccmZp$Xt0>DgZZb%jg7lK8A0@BcBY?_0DX4~H2?qr diff --git a/WinClassicSamplesCS.sln b/WinClassicSamplesCS.sln index 47afc02b..e87ddabd 100644 --- a/WinClassicSamplesCS.sln +++ b/WinClassicSamplesCS.sln @@ -775,6 +775,7 @@ Global {4FBDBE52-2844-47F0-8D2E-CA06234E76A8}.Release|x86.ActiveCfg = Release|Any CPU {4FBDBE52-2844-47F0-8D2E-CA06234E76A8}.Release|x86.Build.0 = Release|Any CPU {42D061DF-6C64-4907-B23B-38EAAE1EC701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42D061DF-6C64-4907-B23B-38EAAE1EC701}.Debug|Any CPU.Build.0 = Debug|Any CPU {42D061DF-6C64-4907-B23B-38EAAE1EC701}.Debug|x64.ActiveCfg = Debug|Any CPU {42D061DF-6C64-4907-B23B-38EAAE1EC701}.Debug|x64.Build.0 = Debug|Any CPU {42D061DF-6C64-4907-B23B-38EAAE1EC701}.Debug|x86.ActiveCfg = Debug|Any CPU From a5256e2f923e3d5dd3ca89681bd7c70a29f8f692 Mon Sep 17 00:00:00 2001 From: dahall Date: Sun, 26 Apr 2026 20:34:23 -0600 Subject: [PATCH 2/2] Renamed authz to authzclisvr and completed impl --- .../security/authorization/authz/authz.cs | 9 - .../security/authorization/authz/authzcli.cs | 123 +++++ .../{authz.csproj => authzclisvr.csproj} | 2 + .../security/authorization/authz/authzsvr.cs | 484 ++++++++++++++++++ .../security/authorization/authz/common.cs | 227 ++++++++ WinClassicSamplesCS.sln | 2 +- 6 files changed, 837 insertions(+), 10 deletions(-) delete mode 100644 Win7Samples/security/authorization/authz/authz.cs create mode 100644 Win7Samples/security/authorization/authz/authzcli.cs rename Win7Samples/security/authorization/authz/{authz.csproj => authzclisvr.csproj} (64%) create mode 100644 Win7Samples/security/authorization/authz/authzsvr.cs create mode 100644 Win7Samples/security/authorization/authz/common.cs diff --git a/Win7Samples/security/authorization/authz/authz.cs b/Win7Samples/security/authorization/authz/authz.cs deleted file mode 100644 index e416121f..00000000 --- a/Win7Samples/security/authorization/authz/authz.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AuthZ; - -internal static class AuthZ -{ - public static int Main(string[] args) - { - return 0; - } -} \ No newline at end of file diff --git a/Win7Samples/security/authorization/authz/authzcli.cs b/Win7Samples/security/authorization/authz/authzcli.cs new file mode 100644 index 00000000..417c1778 --- /dev/null +++ b/Win7Samples/security/authorization/authz/authzcli.cs @@ -0,0 +1,123 @@ +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Common; +using static Vanara.PInvoke.Kernel32; + +internal partial class Program +{ + const string pwd = "Pa$$w0rd"; + static readonly ManualResetEventSlim svrExit = new(false); + static readonly Dictionary ExTypes = new(StringComparer.InvariantCultureIgnoreCase) + { + ["Personal"] = ACCESS_FUND.ACCESS_FUND_PERSONAL, + ["Corporate"] = ACCESS_FUND.ACCESS_FUND_CORPORATE, + ["Transfer"] = ACCESS_FUND.ACCESS_FUND_TRANSFER, + }; + + private static void Main() + { + string szServerName = "\\\\."; + EX_BUF exBuf = default; + + Console.Write("\nRun client as 1) Joe (Employee), 2) Martha (Manager), 3) Bob (VP): "); + var key = Console.ReadKey(); + var userId = key.KeyChar switch + { + '1' => "Joe", + '2' => "Martha", + '3' => "Bob", + _ => null + }; + + Console.Write("\nRun server as 1) Joe (Employee), 2) Martha (Manager), 3) Bob (VP): "); + key = Console.ReadKey(); + SafeLPWSTR serverId = key.KeyChar switch + { + '1' => "Joe", + '2' => "Martha", + '3' => "Bob", + _ => "" + }; + Console.WriteLine(); + + if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(serverId)) + { + Console.WriteLine("Invalid selection. Exiting."); + return; + } + + CreateLocalAcct(userId, pwd); + CreateLocalAcct(serverId!, pwd); + + if (!Impersonate(userId, pwd, out var hClientToken)) + HandleError(GetLastError(), "Impersonate", true, true); + + using var svr = SafeHTHREAD.Create(AuthzSvr, serverId, out _); + + Usage(); + string? input; + while (!string.IsNullOrEmpty(input = Console.ReadLine())) + { + var args = input.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + try + { + // + // Verify expnese input + // + if (args.Length < 2 || !ExTypes.TryGetValue(args[0], out exBuf.dwType) || !int.TryParse(args[1], out int amt) || amt == 0) + { + Usage(); + continue; + } + + Console.Write($"expense: {ExNames[(int)exBuf.dwType]} Ammount: {exBuf.dwAmmount}\n"); + + var szPipeName = $"{szServerName}\\pipe\\AuthzSamplePipe"; + + // Wait for an instance of the pipe + if (!WaitNamedPipe(szPipeName, NMPWAIT_WAIT_FOREVER)) + HandleError(GetLastError(), "WaitNamedPipe", true, true); + + // Connect to pipe + using var hPipe = CreateFile(szPipeName, FileAccess.GENERIC_READ | FileAccess.GENERIC_WRITE, 0, + default, CreationOption.OPEN_EXISTING, 0, default); + if (hPipe.IsInvalid) + HandleError(GetLastError(), "CreateFile", true, true); + + // Send off request + WriteToPipe(hPipe, exBuf); + + // wait till server responds with one uint + if (ReadFromPipe(hPipe, out uint dwResponse) == 0) + { + Console.Write("Error reading form Svr\n"); + return; + } + + switch (dwResponse) + { + case EXPENSE_APPROVED: + Console.Write("Expense Approved.\n"); + break; + + case Win32Error.ERROR_ACCESS_DENIED: + Console.Write("Expense denied: Access denied.\n"); + break; + + case ERROR_INSUFFICIENT_FUNDS: + Console.Write("Expense denied: Insufficient funds.\n"); + break; + + default: + Console.Write("Expense failed: unexpected error.\n"); + break; + } + } + catch { } + } + svrExit.Set(); + svr.Wait(); + + static void Usage() => Console.Write("Usage: \n"); + } +} \ No newline at end of file diff --git a/Win7Samples/security/authorization/authz/authz.csproj b/Win7Samples/security/authorization/authz/authzclisvr.csproj similarity index 64% rename from Win7Samples/security/authorization/authz/authz.csproj rename to Win7Samples/security/authorization/authz/authzclisvr.csproj index 24c9dca4..8b8abaaa 100644 --- a/Win7Samples/security/authorization/authz/authz.csproj +++ b/Win7Samples/security/authorization/authz/authzclisvr.csproj @@ -6,6 +6,8 @@ + + diff --git a/Win7Samples/security/authorization/authz/authzsvr.cs b/Win7Samples/security/authorization/authz/authzsvr.cs new file mode 100644 index 00000000..24618725 --- /dev/null +++ b/Win7Samples/security/authorization/authz/authzsvr.cs @@ -0,0 +1,484 @@ +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Common; +using static Vanara.PInvoke.AdvApi32; +using static Vanara.PInvoke.Authz; +using static Vanara.PInvoke.Kernel32; + +internal partial class Program +{ + private static uint AuthzSvr(IntPtr ptr) + { + if (!Impersonate(Marshal.PtrToStringUni(ptr)!, pwd, out var hSvrToken)) + HandleError(GetLastError(), "Impersonate", true, true); + + if (!TogglePrivileges(["SeSecurityPrivilege"], true)) + HandleError(GetLastError(), "AdjustTokenPrivileges", true, true); + + using FUNDSRM FundsRM = new(200000000); + + SetupNamedPipe(out SafeHPIPE? hPipe, "\\\\.\\pipe\\AuthzSamplePipe"); + + while (!svrExit.IsSet) + { + // Wait for a client... + + if (!ConnectNamedPipe(hPipe)) + HandleError(GetLastError(), "ConnectNamedPipe", true, true); + + try + { + var dwBytesRead = ReadFromPipe(hPipe, out EX_BUF exBuf); + if (dwBytesRead == 0) + Console.Write("Error reading from client\n"); + + // Get Token + + if (!ImpersonateNamedPipeClient(hPipe)) + HandleError(GetLastError(), "ImpersonateNamedPipeClient", true, true); + + if (!GetUserName(out var ClientName)) + HandleError(GetLastError(), "GetUserName", true, true); + + if (!OpenThreadToken(GetCurrentThread(), TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_IMPERSONATE, false, out SafeHTOKEN? hToken)) + HandleError(GetLastError(), "OpenThreadToken", true, true); + + using (hToken) + { + RevertToSelf(); + + Console.Write($"{ClientName} requests {ExNames[(int)exBuf.dwType]} expense of {exBuf.dwAmmount} cents\n"); + + // use token to and context to vaidate - note that this sample uses the token if we just had a user's sid we could build + // an authz context with that. + + if (!FundsRM.AuthorizeAndExecuteExpense(hToken, exBuf, out Win32Error dwResult)) + { + Console.Write($"Error executing expense: {dwResult}\n"); + } + + WriteToPipe(hPipe, dwResult); + } + } + finally + { + DisconnectNamedPipe(hPipe); + } + } + + return 0; + } + + // + // Routine Description: + // Attempts to enable or disable a given privilege. Returns the previous state for the privilege. + // + private static bool TogglePrivileges(string[] privilegeNames, bool enable) + { + using SafeHTOKEN token = SafeHTOKEN.FromThread(GetCurrentThread(), TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY); + var newPriv = new TOKEN_PRIVILEGES(Array.ConvertAll(privilegeNames, s => new LUID_AND_ATTRIBUTES(LUID.FromName(s), enable ? PrivilegeAttributes.SE_PRIVILEGE_ENABLED : 0))); + return AdjustTokenPrivileges(token, false, newPriv, out _).Succeeded; + } +} + +// struct that maintains the state of the fund +internal class FUNDSRM : IDisposable +{ + private const uint MaxSpendingEmployee = 50000; + private const uint MaxSpendingManager = 1000000; + private const uint MaxSpendingVP = 100000000; + + // The amount of money available in the fund + public uint dwFundsAvailable; + + // The resource manager, initialized with the callback functions + public SafeAUTHZ_RESOURCE_MANAGER_HANDLE hRM; + + // The security descriptor for the fund, containing a callback ACE which causes the resource manager callbacks to be used + public SafePSECURITY_DESCRIPTOR SD; + + private static readonly SafePSID EmployeeSid = new([0x00, 0x00, 0x05, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x17, 0xb8, 0x51, 0x59, 0x25, 0x5d, 0x72, 0x66, 0x0b, 0x3b, 0x63, 0x64, 0x00, 0x01, 0x00, 0x03]); + private static readonly SafePSID ManagerSid = new([0x00, 0x00, 0x05, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x17, 0xb8, 0x51, 0x59, 0x25, 0x5d, 0x72, 0x66, 0x0b, 0x3b, 0x63, 0x64, 0x00, 0x01, 0x00, 0x02]); + private static readonly SafePSID VPSid = new([0x00, 0x00, 0x05, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x17, 0xb8, 0x51, 0x59, 0x25, 0x5d, 0x72, 0x66, 0x0b, 0x3b, 0x63, 0x64, 0x00, 0x01, 0x00, 0x01]); + private bool disposedValue; + + public FUNDSRM(uint dwFundsAvailable) + /*++ + + Routine Description + + Initializes the Authz Resource Manager, providing it + with the appropriate callback functions. + It also creates a security descriptor for the fund, allowing only + corporate and transfer expenditures, not personal. Additional logic + could be added to allow VPs to override these restrictions, etc. + + Arguments + + PFUNDSRM pFundsRM - Pointer to a FUNDSRM struct that maintains the state + of the Resource Manager + + uint dwFundsAvailable - The amount of money in the fund managed by this + resource manager + + Return Value + None. + --*/ + { + PSID_IDENTIFIER_AUTHORITY siaWorld = KnownSIDAuthority.SECURITY_WORLD_SID_AUTHORITY; + + // The amount of money in the fund + + this.dwFundsAvailable = dwFundsAvailable; + + // Initialize the fund's resource manager + + if (!AuthzInitializeResourceManager(AuthzResourceManagerFlags.AUTHZ_RM_FLAG_NO_AUDIT | AuthzResourceManagerFlags.AUTHZ_RM_FLAG_INITIALIZE_UNDER_IMPERSONATION, + AuthzAccessCheckCallback, AuthzComputeGroupsCallback, AuthzFreeGroupsCallback, "SampRM", out hRM)) + HandleError(GetLastError(), "AuthzInitializeResourceManager", true, true); + else + Console.Write("Funds Resource Manager initialized - waiting for client\n\n"); + + // Create the fund's security descriptor + + SD = new(Marshal.SizeOf()); + if (!SD.SetGroup(default, false)) + HandleError(GetLastError(), "SetSecurityDescriptorGroup", true, true); + + if (!SD.SetSacl(false, default, false)) + HandleError(GetLastError(), "SetSecurityDescriptorSacl", true, true); + + // an owner must be specified. Since VPs are the highest privileged group this sample we'll make them the owner. + if (!SD.SetOwner(VPSid, false)) + HandleError(GetLastError(), "SetSecurityDescriptorOwner", true, true); + + // Initialize the DACL for the fund + + SafePACL pDaclFund = new(1024, ACL_REVISION_DS); + + // Add an access-allowed ACE for Everyone Only company spending and transfers are allowed for this fund + + // build EVERYONE SID + using (SafePSID psidEveryone = SafePSID.Everyone) + { + using SafePACE pace = new(ACE_TYPE.ACCESS_ALLOWED_CALLBACK_ACE_TYPE, (int)(ACCESS_FUND.ACCESS_FUND_CORPORATE | ACCESS_FUND.ACCESS_FUND_TRANSFER), psidEveryone); + pDaclFund.Add(pace); + } + + // Add that ACL as the security descriptor's DACL + + if (!SD.SetDacl(true, pDaclFund, false)) + HandleError(GetLastError(), "SetSecurityDescriptorDacl", true, true); + } + + ~FUNDSRM() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public bool AuthorizeAndExecuteExpense(HTOKEN hToken, in EX_BUF exBuf, out Win32Error pdwResult) + /*++ + + Routine Description + + Setups the Authz context and makes call to AuthzAccessCheck. Then + modifies the remaining funds depending on acccess and ammount + + Arguments + + PFUNDSRM pFundsRM - Pointer to a FUNDSRM struct that maintains the state + of the Resource Manager + + HANDLE hToken - The token representing the user were doing the access + check for + + EX_BUF exBuf - struct that contains desired access and expense ammount + + ref uint pdwResult - Pointer to uint to put result/error + + Return Value + bool True if no errors. + --*/ + { + // first we need an Authz context + + if (!AuthzInitializeContextFromToken(0, + hToken, + hRM, + default, + default, + default, + out SafeAUTHZ_CLIENT_CONTEXT_HANDLE? AuthzClient)) + { + HandleError(pdwResult = GetLastError(), "AuthzInitializeContextFromToken", true, false); + return false; + } + + try + { + // Do AccessCheck + AUTHZ_ACCESS_REQUEST AccessRequest = new() + { + DesiredAccess = (int)exBuf.dwType, + PrincipalSelfSid = default, + ObjectTypeList = default, + ObjectTypeListLength = 0, + OptionalArguments = (int)exBuf.dwAmmount, + }; + + // The ResultListLength is set to the number of ObjectType GUIDs in the Request, indicating that the caller would like + // detailed information about granted access to each node in the tree. + AUTHZ_ACCESS_REPLY AccessReply = new(1) + { + GrantedAccessMaskValues = [0], + ErrorValues = [0] + }; + + if (!AuthzAccessCheck(0, + AuthzClient, + AccessRequest, + default, + SD, + default, + 0, + AccessReply, + default)) + { + HandleError(pdwResult = GetLastError(), "AuthzAccessCheck", true, false); + return false; + } + + if ((AccessReply.GrantedAccessMaskValues[0] & (uint)exBuf.dwType) != (uint)exBuf.dwType) + { + // Access is denied get error from reply if there else Getlasterror. + pdwResult = AccessReply.ErrorValues[0]; + Console.Write("Access denied\n\n"); + } + else + { + // Access is granted, is there enough funds, this could have been done within the AuthzAccessCheckCallback but since this + // is more of an execution problem than access checking we'll do it here. + if (dwFundsAvailable < exBuf.dwAmmount) + { + // not enough in ref fund + pdwResult = ERROR_INSUFFICIENT_FUNDS; + Console.Write("Failed : NSF\n\n"); + } + else + { + dwFundsAvailable -= exBuf.dwAmmount; + pdwResult = EXPENSE_APPROVED; + Console.Write("Expense Approved. Remaining funds:{0} cents.\n\n", dwFundsAvailable); + } + } + + return true; + } + finally + { + AuthzClient?.Dispose(); + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + hRM.Dispose(); + SD.Dispose(); + disposedValue = true; + } + } + + private static bool AuthzAccessCheckCallback([In] AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClientContext, [In] PACE pAce, [In] IntPtr pArgs, ref bool pbAceApplicable) + + /*++ + + Routine Description + + This is the callback access check. It is registered with a + resource manager. AuthzAccessCheck calls this function when it + encounters a callback type ACE, one of: + ACCESS_ALLOWED_CALLBACK_ACE_TYPE + ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE + ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE + + This function determines if the given callback ACE applies to the + client context (which has already had dynamic groups computed) and + the optional arguments, in this case the request amount. + + The list of groups which apply to the user is traversed. If a group + is found which allows the user the requested access, pbAceApplicable + is set to true and the function returns. If the end of the group list + is reached, pbAceApplicable is set to false and the function returns. + + Arguments + + hAuthzClientContext - handle to the AuthzClientContext. + + pAce - pointer to the Ace header. + + pArgs - optional arguments, in this case ref uint , uint is the spending + request amount in cents + + pbAceApplicable - returns true iff the ACE allows the client's request + + Return value + + Bool, true on success, false on error + + --*/ + { + uint dwRequestedSpending = (uint)pArgs.ToInt32(); + + // By default, the ACE does not apply to the request + + pbAceApplicable = false; + + // The object's access mask (right after the ACE_HEADER) The access mask determines types of expenditures allowed from this fund + + //uint pAccessMask = pAce.GetMask(); + + // Get the TOKEN_GROUPS array + + TOKEN_GROUPS pvTokenGroupsBuf; + try { pvTokenGroupsBuf = AuthzGetInformationFromContext(hAuthzClientContext, AUTHZ_CONTEXT_INFORMATION_CLASS.AuthzContextInfoGroupsSids); } + catch { return false; } + + // Go through the groups until end is reached or a group applying to the request is found + + for (int i = 0; i < pvTokenGroupsBuf.GroupCount && pbAceApplicable != true; i++) + { + // This is the business logic. Each level of employee can approve different amounts. + + // VP + + if (VPSid.Equals(pvTokenGroupsBuf.Groups[i].Sid) && dwRequestedSpending <= MaxSpendingVP) + { + pbAceApplicable = true; + } + + // Manager + + if (ManagerSid.Equals(pvTokenGroupsBuf.Groups[i].Sid) && dwRequestedSpending <= MaxSpendingManager) + { + pbAceApplicable = true; + } + + // Employee + + if (EmployeeSid.Equals(pvTokenGroupsBuf.Groups[i].Sid) && dwRequestedSpending <= MaxSpendingEmployee) + { + pbAceApplicable = true; + } + } + + // return true when access check completed (when a callback ace applies not) If we had a runtime error, such as mem alloc errors + // we would return false + return true; + } + + private static bool AuthzComputeGroupsCallback(AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClientContext, nint pArgs, out nint pSidAttrArray, out uint pSidCount, out nint pRestrictedSidAttrArray, out uint pRestrictedSidCount) + /*++ + + Routine Description + + Resource manager callback to compute dynamic groups. This is used by the RM + to decide if the specified client context should be included in any RM defined groups. + + In this example, the employees are hardcoded into their roles. However, this is the + place you would normally retrieve data from an external source to determine the + users' additional roles. + + Arguments + + hAuthzClientContext - handle to client context. + Args - optional parameter to pass information for evaluating group membership. + pSidAttrArray - computed group membership SIDs + pSidCount - count of SIDs + pRestrictedSidAttrArray - computed group membership restricted SIDs + pRestrictedSidCount - count of restricted SIDs + + Return Value + + Bool, true for success, false on failure. + + --*/ + { + pSidAttrArray = pRestrictedSidAttrArray = default; + pSidCount = pRestrictedSidCount = 0; + + // First, look up the user's SID from the context + + // Get the SID (inside a TOKEN_USER structure) + + TOKEN_USER pvSidBuf; + try { pvSidBuf = AuthzGetInformationFromContext(hAuthzClientContext, AUTHZ_CONTEXT_INFORMATION_CLASS.AuthzContextInfoUserSid); } + catch { return false; } + + // The hardcoded Sample logic: + // + // Lookup the sid to get the username and grant dynamic sid based on username + // + // Bob is a VP Martha is a Manager Joe is an Employee + + if (!LookupAccountSid(default, pvSidBuf.User.Sid.GetBinaryForm(), out var UserName, out _, out _)) + { + HandleError(GetLastError(), "LookupAccountSid", true, true); + } + + PSID userSid = UserName!.ToLowerInvariant() switch + { + "bob" => VPSid, + "martha" => ManagerSid, + "joe" => EmployeeSid, + _ => PSID.NULL + }; + if (userSid != PSID.NULL) + { + // Allocate the memory for the returns, which will be deallocated by FreeDynamicGroups Only a single group will be returned, + // determining the employee type + + pSidCount = 1; + // No restricted group sids + SafeHGlobalStruct pSidAttrArrayBuf = new SID_AND_ATTRIBUTES(userSid, (uint)GroupAttributes.SE_GROUP_ENABLED); + pSidAttrArray = pSidAttrArrayBuf.ReleaseOwnership(); + } + + return true; + } + + private static void AuthzFreeGroupsCallback(nint pSidAttrArray) + /*++ + + Routine Description + + Frees memory allocated for the dynamic group array. + + Arguments + + pSidAttrArray - array to free. + + Return Value + None. + --*/ + { + if (pSidAttrArray != IntPtr.Zero) + { + Marshal.FreeHGlobal(pSidAttrArray); + } + } +} \ No newline at end of file diff --git a/Win7Samples/security/authorization/authz/common.cs b/Win7Samples/security/authorization/authz/common.cs new file mode 100644 index 00000000..e693c230 --- /dev/null +++ b/Win7Samples/security/authorization/authz/common.cs @@ -0,0 +1,227 @@ +using Vanara.Extensions; +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.AdvApi32; +using static Vanara.PInvoke.Kernel32; +using static Vanara.PInvoke.NetApi32; +//using static Vanara.PInvoke.User32; + +public static class Common +{ + /////////////////////////////////////////////////////////////////////////////// + // Access flags for funds RM + // + /////////////////////////////////////////////////////////////////////////////// + + // + // Expense failed insufficient funds + // + public const int ERROR_INSUFFICIENT_FUNDS = 0x20000002; + + // + // Expense approved and subtracted from fund. + // + public const int EXPENSE_APPROVED = 0; + + // + // Expense failed due to an unknown error + // + public const int EXPENSE_UNKNOWN_ERROR = 0x20000003; + + // + // Error with bit 29 set are private errors (see SetLastError doc) + // + public const int PRIVATE_ERROR_BIT = 0x20000000; + + public static readonly string[] ExNames = ["", "PERSONAL", "CORPORATE", "", "TRANSFER"]; + + [Flags] + public enum ACCESS_FUND : uint + { + // Personal expenditures + ACCESS_FUND_PERSONAL = 0x00000001, + // Company spending + ACCESS_FUND_CORPORATE = 0x00000002, + // Transfer to other funds + ACCESS_FUND_TRANSFER = 0x00000004, + } + + + /////////////////////////////////////////////////////////////////////////////// + // Codes for expense access attempts + // + /////////////////////////////////////////////////////////////////////////////// + // + // We'll use existing Win32Error.Win32Error.Win32Error.ERROR_ACCESS_DENIED for access denied. + // + // ERROR_ACCESS_DENIED = 0x00000005 + /////////////////////////////////////////////////////////////////////////////// + // Expense request packing struct + // + /////////////////////////////////////////////////////////////////////////////// + + public static bool BuildGenericAccessAcl(out SafePACL ppAcl) + /*++ + + Routine Description + + This function builds a Dacl which grants the creator of the objects + GENERIC_ALL (Full Control) and Everyone GENERIC_READ, GENERIC_WRITE and + GENERIC_EXECUTE access to the object. + + This Dacl allows for higher security than a default Dacl, as this only grants + the creator/owner write access to the security descriptor, and grants + Everyone the ability to "use" the object. This scenario prevents a + malevolent user from disrupting service by preventing arbitrary access + manipulation. + + Arguments + + ref PACL pAcl - Pointer to buffer for pointer to allocated PACL. Must be + freed with LocalFree + + ref uint cbAclSize - Pointer to dword receiving size of acl. + + Return value + + Bool, true on success, false on error + + --*/ + { + // + // build well known sids + // + + // build EVERYONE SID + using SafePSID pEveryoneSid = SafePSID.Everyone; + + // build Creator/Owner SID + AllocateAndInitializeSid(KnownSIDAuthority.SECURITY_CREATOR_SID_AUTHORITY, 1, KnownSIDRelativeID.SECURITY_CREATOR_OWNER_RID, 0, 0, 0, 0, 0, 0, 0, out var pOwnerSid); + + ppAcl = new SafePACL([ + new SafePACE(ACE_TYPE.ACCESS_ALLOWED_ACE_TYPE, ACCESS_MASK.GENERIC_READ | ACCESS_MASK.GENERIC_WRITE | ACCESS_MASK.GENERIC_EXECUTE, pEveryoneSid), + new SafePACE(ACE_TYPE.ACCESS_ALLOWED_ACE_TYPE, ACCESS_MASK.GENERIC_ALL, pOwnerSid), + ]); + + return true; + } + + public static bool CreateLocalAcct(string pszName, string pszPassword, UserPrivilege priv = UserPrivilege.USER_PRIV_USER, UserAcctCtrlFlags flags = 0) + { + USER_INFO_1 ui = new() { usri1_name = pszName, usri1_password = pszPassword, usri1_priv = priv, usri1_flags = flags }; + try { NetUserAdd(null, ui); return true; } catch (Exception ex) { return ex.HResult == ((Win32Error)Win32Error.NERR_UserExists).ToHRESULT(); } + } + + public static bool Impersonate(string pszName, string pszPassword, out SafeHTOKEN hToken) + { + if (!LogonUser(pszName, ".", pszPassword, LogonUserType.LOGON32_LOGON_INTERACTIVE, LogonUserProvider.LOGON32_PROVIDER_DEFAULT, out hToken)) + return false; + return ImpersonateLoggedOnUser(hToken); + } + + ////////////////////////////////////////////////////////////////////// + public static Win32Error DisplayAPIError(string pszAPI, bool bConsole, bool bMsgBox, bool bExit) + { + var dwError = Win32Error.GetLastError(); + + //... now display this string + var szErrMsgBuffer = $"ERROR: API = {pszAPI}.\nERROR CODE = {(uint)dwError} (0x{(uint)dwError:X}).\nMESSAGE = {dwError}"; + + if (bConsole) + Console.Write(szErrMsgBuffer); + //if (bMsgBox) + // MessageBox(GetDesktopWindow(), szErrMsgBuffer, "Execution Error", MB_FLAGS.MB_OK); + + OutputDebugString(szErrMsgBuffer); + + if (bExit) + ExitProcess((uint)dwError); + + return dwError; + } + + public static void HandleError(Win32Error dwErr, string pszAPI, bool fAPI, bool fExit) + { + if (fAPI) + { + DisplayAPIError(pszAPI, true, true, fExit); + } + else + { + Console.Write(pszAPI); + if (dwErr.Failed) + Console.Write($"{(uint)dwErr}\n"); + + if (fExit) + ExitProcess(0); + } + + return; + } + + public static uint ReadFromPipe(HFILE hPipe, out T pBuffer) where T : struct + { + var bRet = ReadFile(hPipe, out pBuffer); + if (!bRet) + { + var dwErr = GetLastError(); + // if ERROR_BROKEN_PIPE or ERROR_NO_DATA then pipe naturally ended + if ((uint)dwErr is not (Win32Error.ERROR_BROKEN_PIPE or Win32Error.ERROR_NO_DATA)) + HandleError(dwErr, "ReadFile", true, true); + } + + return InteropExtensions.SizeOf(); + } + + public static bool SetupNamedPipe(out SafeHPIPE phPipe, string szPipeName) + { + phPipe = SafeHPIPE.Null; + SafePSECURITY_DESCRIPTOR sd = new(Marshal.SizeOf()); + + if (!BuildGenericAccessAcl(out var pDacl)) + { + HandleError(0, "Error setting up pipe dacl", false, true); + return false; + } + + if (!sd.SetDacl(true, pDacl, false)) + { + HandleError(GetLastError(), "SetSecurityDescriptorDacl", true, true); + return false; + } + + SECURITY_ATTRIBUTES sa = new() { lpSecurityDescriptor = sd }; + + // setup pipe and wait for a ref connection + phPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS.FILE_FLAG_OVERLAPPED | PIPE_ACCESS.PIPE_ACCESS_DUPLEX, + PIPE_TYPE.PIPE_TYPE_MESSAGE | PIPE_TYPE.PIPE_READMODE_MESSAGE | PIPE_TYPE.PIPE_WAIT, 1, 0, 0, + NMPWAIT_USE_DEFAULT_WAIT, sa); + if (phPipe.IsInvalid) + { + HandleError(GetLastError(), "CreateNamedPipe", true, true); + return false; + } + + return true; + } + + public static bool WriteToPipe(HFILE hPipe, T pData) where T : struct + { + using var pDataBuffer = SafeHGlobalHandle.CreateFromStructure(pData); + var bRet = WriteFile(hPipe, pDataBuffer, (uint)pDataBuffer.Size, out var nBytesWrote); + if (!bRet) + { + var dwErr = GetLastError(); + // if ERROR_BROKEN_PIPE or ERROR_NO_DATA then pipe naturally ended + if ((uint)dwErr is not (Win32Error.ERROR_BROKEN_PIPE or Win32Error.ERROR_NO_DATA)) + HandleError(dwErr, "WriteFile", true, true); + } + return bRet; + } + + public struct EX_BUF + { + public uint dwAmmount; + public ACCESS_FUND dwType; + } +} \ No newline at end of file diff --git a/WinClassicSamplesCS.sln b/WinClassicSamplesCS.sln index e87ddabd..97b1ef7a 100644 --- a/WinClassicSamplesCS.sln +++ b/WinClassicSamplesCS.sln @@ -53,7 +53,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aclapi", "Win7Samples\secur EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "audit", "Win7Samples\security\authorization\audit\audit.csproj", "{B5CB45DC-4DED-4EE2-9242-3F7B0EAEAF58}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "authz", "Win7Samples\security\authorization\authz\authz.csproj", "{86EB6286-8842-4D4C-896B-BB7E5BB5D6D6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "authzclisvr", "Win7Samples\security\authorization\authz\authzclisvr.csproj", "{86EB6286-8842-4D4C-896B-BB7E5BB5D6D6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzMigrate", "Win7Samples\security\authorization\azman\AzMigrate.csproj", "{69DA869D-1797-4417-8102-F02D3AD7C029}" EndProject