diff --git a/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll b/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll
index a389d683..e924394d 100644
Binary files a/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll and b/DPIAwarenessPerWindow/client/DpiAwarenessContextRes.dll differ
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5ff1a659..3fa263d8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,7 +3,7 @@
true
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/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/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll b/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll
index a9dec098..d3eb6a78 100644
Binary files a/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll and b/Win7Samples/winui/shell/appplatform/DragDropVisuals/DragDropVisualsRes.dll differ
diff --git a/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll b/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll
index 44fd469d..69eb0d10 100644
Binary files a/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll and b/Win7Samples/winui/shell/appplatform/FIleOperationProgressSink/ProgressSinkSampleAppRes.dll differ
diff --git a/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll b/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll
index aee02852..95e1c99f 100644
Binary files a/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll and b/Win7Samples/winui/shell/appplatform/NamespaceTreeControl/NamespaceTreeSDKSampleRes.dll differ
diff --git a/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll b/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll
index d8f98ef0..37792f7a 100644
Binary files a/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll and b/Win7Samples/winui/shell/appplatform/ShellLibraryBackup/ShellLibraryBackupRes.dll differ
diff --git a/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll b/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll
index 86a7fe0a..84321dbc 100644
Binary files a/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll and b/Win7Samples/winui/speech/SimpleDictation/SimpleDictationRes.dll differ
diff --git a/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll b/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll
index ca2475e9..b59fe292 100644
Binary files a/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll and b/Win7Samples/winui/speech/SimpleTelephony/SimpleTelephonyRes.dll differ
diff --git a/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll b/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll
index 2f26d6d9..d49f4ada 100644
Binary files a/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll and b/Win7Samples/winui/speech/TtsApplication/TtsApplicationRes.dll differ
diff --git a/Win7Samples/winui/speech/talkback/TalkBackRes.dll b/Win7Samples/winui/speech/talkback/TalkBackRes.dll
index deed6aea..62ba3b48 100644
Binary files a/Win7Samples/winui/speech/talkback/TalkBackRes.dll and b/Win7Samples/winui/speech/talkback/TalkBackRes.dll differ
diff --git a/WinClassicSamplesCS.sln b/WinClassicSamplesCS.sln
index 47afc02b..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
@@ -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