microsoft / artifacts-credprovider

The Azure Artifacts Credential Provider enables dotnet, NuGet.exe, and MSBuild to interactively acquire credentials for Azure Artifacts feeds.
MIT License
747 stars 709 forks source link

MsalInteractiveTokenProvider breaks if no console window handle available #472

Closed TomKuhn closed 1 month ago

TomKuhn commented 5 months ago

We have a Windows Forms app (it's basically a setup wizard type application) At some point, this program uses powershell.exe to run a script which does a NuGet restore from a private ADO feed. This invokes CredentialProvider.Microsoft.exe to get a token for the Nuget ADO feed

However, because this wizard is a Windows Forms app, and we don't want random cmd console windows popping up all over the place as we run external programs, we always launch them with CreateNoWindow, therefor the child processes get NO ConsoleWindow.

This causes the provider to not be able to use Interactive flow because:

AzureArtifacts.cs IntPtr consoleHandle = GetConsoleWindow(); returns IntPtr.Zero.

Do you have any solution for a use case like this? CredentialProvider.Microsoft.exe launched from a Forms app, and no console window is available?

I've hacked around and added the following code to CredentialProvider, but I'd rather not have to modify this code and have our own private copy!

private static IntPtr GetConsoleOrTerminalWindow() { IntPtr consoleHandle = GetConsoleWindow(); IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);

if (handle == IntPtr.Zero) { // Cannot get handle to console window, walk up parent processes until we find one that has a MainWindowHandle var parent = ParentProcessUtilities.GetParentProcess(); while (parent != null) { if (parent.MainWindowHandle != IntPtr.Zero) { handle = parent.MainWindowHandle; break; } parent = ParentProcessUtilities.GetParentProcess(parent.Handle); } }

return handle; }

///

/// A utility class to determine a process parent. ///

[StructLayout(LayoutKind.Sequential)] public struct ParentProcessUtilities { // These members must match PROCESS_BASIC_INFORMATION internal IntPtr Reserved1; internal IntPtr PebBaseAddress; internal IntPtr Reserved2_0; internal IntPtr Reserved2_1; internal IntPtr UniqueProcessId; internal IntPtr InheritedFromUniqueProcessId; [DllImport("ntdll.dll")] private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);

///

/// Gets the parent process of the current process. /// /// An instance of the Process class. public static Process GetParentProcess() { return GetParentProcess(Process.GetCurrentProcess().Handle); }

///

/// Gets the parent process of specified process. /// /// The process id. /// An instance of the Process class. public static Process GetParentProcess(int id) { Process process = Process.GetProcessById(id); return GetParentProcess(process.Handle); }

///

/// Gets the parent process of a specified process. /// /// The process handle. /// An instance of the Process class. public static Process GetParentProcess(IntPtr handle) { ParentProcessUtilities pbi = new ParentProcessUtilities(); int returnLength; int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength); if (status != 0) throw new Win32Exception(status);

try
{
    return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
    // not found
    return null;
}

} }

embetten commented 5 months ago

@TomKuhn - to make sure I understand your use case:

You want the cred provider interactive prompt pop-up windows, and device code flow is not possible or wanted? But the parent process does not have access to the console window, but the parent process of the parent process does?

TomKuhn commented 5 months ago

@embetten - Some clarification for you

My PR just was a trial piece of code that works for us, that if we don't have a console window, then maybe we can get the window handle from the process which launched us. It could probably be refactored better into a list of IParentWindowHandleProvider type classes, trying each one in turn until one returns an HWND.

To answer your questions above directly: You want the cred provider interactive prompt pop-up windows, and device code flow is not possible or wanted? Yes, exactly

But the parent process does not have access to the console window, but the parent process of the parent process does? The current process might have no ConsoleWindow(), but the parent process (or parent of parent etc) does have a window (not necessarily a Console, might be a WinForm, might be a native app, but all you need is the hWnd)

embetten commented 5 months ago

@TomKuhn

Thanks for the additional information. Looking into the area more, it looks like the dotnet auth team is moving some of this logic into MSAL see. In addition, looking at the PR, https://github.com/microsoft/artifacts-credprovider/pull/473, we have concerns about prompting users from the wrong handler in non-windows form scenarios.

We are considering an alternate approach to allow users to pass the appropriate handle as an input and will keep this issue open as an enhancement to track that work. For this approach, please let us know if you are running the cred provider from a nuget.exe, dotnet, or in standalone mode.

github-actions[bot] commented 2 months ago

This issue has had no activity in 90 days. Please comment if it is not actually stale.