diff --git a/src/Files.App/Filesystem/NetworkDrivesAPI.cs b/src/Files.App/Filesystem/NetworkDrivesAPI.cs index 0e4c031a4690..4fcd4b8a06cc 100644 --- a/src/Files.App/Filesystem/NetworkDrivesAPI.cs +++ b/src/Files.App/Filesystem/NetworkDrivesAPI.cs @@ -1,11 +1,16 @@ -using Files.App.Shell; +using Files.App.Extensions; +using Files.App.Helpers; +using Files.App.Shell; using System; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Vanara.Extensions; using Vanara.InteropServices; +using static Vanara.PInvoke.AdvApi32; +using Vanara.PInvoke; using static Vanara.PInvoke.Mpr; namespace Files.App.Filesystem @@ -117,6 +122,52 @@ public static Task OpenMapNetworkDriveDialog(long hwnd) }); } + public static async Task AuthenticateNetworkShare(string path) + { + NETRESOURCE nr = new NETRESOURCE + { + dwType = NETRESOURCEType.RESOURCETYPE_DISK, + lpRemoteName = path + }; + + Win32Error connectionError = WNetAddConnection3(HWND.NULL, nr, null, null, 0); // if creds are saved, this will return NO_ERROR + + if (connectionError == Win32Error.ERROR_LOGON_FAILURE) + { + var dialog = DynamicDialogFactory.GetFor_CredentialEntryDialog(path); + await dialog.ShowAsync(); + var credentialsReturned = dialog.ViewModel.AdditionalData as string[]; + + if (credentialsReturned is string[] && credentialsReturned[1] != null) + { + connectionError = WNetAddConnection3(HWND.NULL, nr, credentialsReturned[1], credentialsReturned[0], 0); + if (credentialsReturned[2] == "y" && connectionError == Win32Error.NO_ERROR) + { + CREDENTIAL creds = new CREDENTIAL(); + creds.TargetName = new StrPtrAuto(path.Substring(2)); + creds.UserName = new StrPtrAuto(credentialsReturned[0]); + creds.Type = CRED_TYPE.CRED_TYPE_DOMAIN_PASSWORD; + creds.AttributeCount = 0; + creds.Persist = CRED_PERSIST.CRED_PERSIST_ENTERPRISE; + byte[] bpassword = Encoding.Unicode.GetBytes(credentialsReturned[1]); + creds.CredentialBlobSize = (UInt32)bpassword.Length; + creds.CredentialBlob = Marshal.StringToCoTaskMemUni(credentialsReturned[1]); + CredWrite(creds, 0); + } + } + else + return false; + } + + if (connectionError == Win32Error.NO_ERROR) + return true; + else + { + await DialogDisplayHelper.ShowDialogAsync("NetworkFolderErrorDialogTitle".GetLocalizedResource(), connectionError.ToString().Split(":")[1].Trim()); + return false; + } + } + public static bool DisconnectNetworkDrive(string drive) { return WNetCancelConnection2(drive.TrimEnd('\\'), CONNECT.CONNECT_UPDATE_PROFILE, true).Succeeded; diff --git a/src/Files.App/Helpers/DynamicDialogFactory.cs b/src/Files.App/Helpers/DynamicDialogFactory.cs index 6e2d7aa7a213..b9a5d3d8aee7 100644 --- a/src/Files.App/Helpers/DynamicDialogFactory.cs +++ b/src/Files.App/Helpers/DynamicDialogFactory.cs @@ -60,7 +60,6 @@ public static DynamicDialog GetFor_RenameDialog() DynamicDialog? dialog = null; TextBox inputText = new() { - Height = 35d, PlaceholderText = "RenameDialogInputText/PlaceholderText".GetLocalizedResource() }; @@ -134,5 +133,83 @@ public static DynamicDialog GetFor_FileInUseDialog(List loc }); return dialog; } + + public static DynamicDialog GetFor_CredentialEntryDialog(string path) + { + string[] userAndPass = new string[3]; + DynamicDialog? dialog = null; + + TextBox inputUsername = new() + { + PlaceholderText = "CredentialDialogUserName/PlaceholderText".GetLocalizedResource() + }; + + PasswordBox inputPassword = new() + { + PlaceholderText = "CredentialDialogPassword/PlaceholderText".GetLocalizedResource() + }; + + CheckBox saveCreds = new() + { + Content = "NetworkAuthenticationSaveCheckbox".GetLocalizedResource() + }; + + inputUsername.TextChanged += (textBox, args) => + { + userAndPass[0] = inputUsername.Text; + dialog.ViewModel.AdditionalData = userAndPass; + }; + + inputPassword.PasswordChanged += (textBox, args) => + { + userAndPass[1] = inputPassword.Password; + dialog.ViewModel.AdditionalData = userAndPass; + }; + + saveCreds.Checked += (textBox, args) => + { + userAndPass[2] = "y"; + dialog.ViewModel.AdditionalData = userAndPass; + }; + + saveCreds.Unchecked += (textBox, args) => + { + userAndPass[2] = "n"; + dialog.ViewModel.AdditionalData = userAndPass; + }; + + dialog = new DynamicDialog(new DynamicDialogViewModel() + { + TitleText = "NetworkAuthenticationDialogTitle".GetLocalizedResource(), + PrimaryButtonText = "AskCredentialDialog/PrimaryButtonText".GetLocalizedResource(), + CloseButtonText = "Cancel".GetLocalizedResource(), + SubtitleText = string.Format("NetworkAuthenticationDialogMessage".GetLocalizedResource(), path.Substring(2)), + DisplayControl = new Grid() + { + MinWidth = 250d, + Children = + { + new StackPanel() + { + Spacing = 10d, + Children = + { + inputUsername, + inputPassword, + saveCreds + } + } + } + }, + CloseButtonAction = (vm, e) => + { + dialog.ViewModel.AdditionalData = null; + vm.HideDialog(); + } + + }); + + return dialog; + } } } \ No newline at end of file diff --git a/src/Files.App/Helpers/NavigationHelpers.cs b/src/Files.App/Helpers/NavigationHelpers.cs index bfd3b03e2bad..bc016bb62b6f 100644 --- a/src/Files.App/Helpers/NavigationHelpers.cs +++ b/src/Files.App/Helpers/NavigationHelpers.cs @@ -284,6 +284,7 @@ private static async Task OpenDirectory(string path, IShellPag var opened = (FilesystemResult)false; bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); + bool isNetwork = path.StartsWith(@"\\", StringComparison.Ordinal); if (isShortcut) { @@ -330,6 +331,27 @@ private static async Task OpenDirectory(string path, IShellPag opened = (FilesystemResult)true; } + else if (isNetwork) + { + var auth = await NetworkDrivesAPI.AuthenticateNetworkShare(path); + if (auth) + { + if (forceOpenInNewTab || userSettingsService.FoldersSettingsService.OpenFoldersInNewTab) + { + await OpenPathInNewTab(path); + } + else + { + associatedInstance.ToolbarViewModel.PathControlDisplayText = path; + associatedInstance.NavigateWithArguments(associatedInstance.InstanceViewModel.FolderSettings.GetLayoutType(path), new NavigationArguments() + { + NavPathParam = path, + AssociatedTabInstance = associatedInstance + }); + } + opened = (FilesystemResult)true; + } + } else { opened = await associatedInstance.FilesystemViewModel.GetFolderWithPathFromPathAsync(path) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index f6c8137c3e16..ba86ab3629d4 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -2900,5 +2900,17 @@ Display the edit tags flyout + + + Enter your credentials to connect to: {0} + + + Enter Network Credentials + + + Remember my credentials + + + Network folder error \ No newline at end of file diff --git a/src/Files.App/ViewModels/ToolbarViewModel.cs b/src/Files.App/ViewModels/ToolbarViewModel.cs index 10ac1109e857..6d6234d5394a 100644 --- a/src/Files.App/ViewModels/ToolbarViewModel.cs +++ b/src/Files.App/ViewModels/ToolbarViewModel.cs @@ -950,6 +950,13 @@ public async Task CheckPathInput(string currentInput, string currentSelectedPath if (currentInput.StartsWith('\\') && !currentInput.StartsWith("\\\\", StringComparison.Ordinal)) currentInput = currentInput.Insert(0, "\\"); + if (currentInput.StartsWith('\\')) + { + var auth = await NetworkDrivesAPI.AuthenticateNetworkShare(currentInput); + if (!auth) + return; + } + if (currentSelectedPath == currentInput || string.IsNullOrWhiteSpace(currentInput)) return;