diff --git a/plugins/hosts/windows/cap/smb.rb b/plugins/hosts/windows/cap/smb.rb index 457807f9c..17fae3d69 100644 --- a/plugins/hosts/windows/cap/smb.rb +++ b/plugins/hosts/windows/cap/smb.rb @@ -17,6 +17,16 @@ module VagrantPlugins true end + def self.smb_validate_password(env, machine, username, password) + script_path = File.expand_path("../../scripts/check_credentials.ps1", __FILE__) + args = [] + args << "-username" << "'#{username.gsub("'", "''")}'" + args << "-password" << "'#{password.gsub("'", "''")}'" + + r = Vagrant::Util::PowerShell.execute(script_path, *args) + r.exit_code == 0 + end + def self.smb_cleanup(env, machine, opts) script_path = File.expand_path("../../scripts/unset_share.ps1", __FILE__) diff --git a/plugins/hosts/windows/plugin.rb b/plugins/hosts/windows/plugin.rb index 38691d0a8..ffe1e3a17 100644 --- a/plugins/hosts/windows/plugin.rb +++ b/plugins/hosts/windows/plugin.rb @@ -46,6 +46,11 @@ module VagrantPlugins Cap::SMB end + host_capability("windows", "smb_cleanup") do + require_relative "cap/smb" + Cap::SMB + end + host_capability("windows", "configured_ip_addresses") do require_relative "cap/configured_ip_addresses" Cap::ConfiguredIPAddresses diff --git a/plugins/synced_folders/smb/errors.rb b/plugins/synced_folders/smb/errors.rb index 761924207..423853b1b 100644 --- a/plugins/synced_folders/smb/errors.rb +++ b/plugins/synced_folders/smb/errors.rb @@ -26,6 +26,10 @@ module VagrantPlugins error_key(:name_error) end + class CredentialsRequestError < SMBError + error_key(:credentials_request_error) + end + class DefineShareFailed < SMBError error_key(:define_share_failed) end diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index e834367df..4a95a0da5 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -11,6 +11,10 @@ require_relative "errors" module VagrantPlugins module SyncedFolderSMB class SyncedFolder < Vagrant.plugin("2", :synced_folder) + + # Maximum number of times to retry requesting username/password + CREDENTIAL_RETRY_MAX = 5 + def initialize(*args) super @@ -43,28 +47,27 @@ module VagrantPlugins end end - script_path = File.expand_path("../scripts/check_credentials.ps1", __FILE__) - if !have_auth machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ") - auth_success = false - while !auth_success do - @creds[:username] = machine.ui.ask("Username: ") - @creds[:password] = machine.ui.ask("Password (will be hidden): ", echo: false) + retries = 0 + while retries < CREDENTIAL_RETRY_MAX do + smb_username = machine.ui.ask("Username: ") + smb_password = machine.ui.ask("Password (will be hidden): ", echo: false) + auth_success = true - args = [] - args << "-username" << "'#{@creds[:username].gsub("'", "''")}'" - args << "-password" << "'#{@creds[:password].gsub("'", "''")}'" - - r = Vagrant::Util::PowerShell.execute(script_path, *args) - - if r.exit_code == 0 - auth_success = true + if machine.env.host.capability?(:smb_validate_password) + Vagrant::Util::CredentialScrubber.sensitive(smb_password) + auth_success = machine.env.host.capability(:smb_validate_password, + smb_username, smb_password) end - if !auth_success - machine.ui.output(I18n.t("vagrant_sf_smb.incorrect_credentials") + "\n ") - end + break if auth_success + machine.ui.output(I18n.t("vagrant_sf_smb.incorrect_credentials") + "\n ") + retries += 1 + end + + if retries >= CREDENTIAL_RETRY_MAX + raise Errors::CredentialsRequestError end end @@ -73,8 +76,6 @@ module VagrantPlugins machine.env.host.capability(:smb_start) end - script_path = File.expand_path("../scripts/set_share.ps1", __FILE__) - folders.each do |id, data| data[:smb_username] ||= smb_username data[:smb_password] ||= smb_password diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml index 5a7dd57b4..0dbf7ac1d 100644 --- a/templates/locales/synced_folder_smb.yml +++ b/templates/locales/synced_folder_smb.yml @@ -39,6 +39,9 @@ en: Vagrant SMB synced folders require the account password to be stored in an NT compatible format. Please update your sharing settings to enable a Windows compatible password and try again. + credentials_request_error: |- + Vagrant failed to receive credential information required for preparing + an SMB share. define_share_failed: |- Exporting an SMB share failed! Details about the failure are shown below. Please inspect the error message and correct any problems. diff --git a/test/unit/plugins/synced_folders/smb/synced_folder_test.rb b/test/unit/plugins/synced_folders/smb/synced_folder_test.rb index a80d45a3a..0b1e9c85d 100644 --- a/test/unit/plugins/synced_folders/smb/synced_folder_test.rb +++ b/test/unit/plugins/synced_folders/smb/synced_folder_test.rb @@ -71,8 +71,8 @@ describe VagrantPlugins::SyncedFolderSMB::SyncedFolder do context "without credentials provided" do before do - expect(machine.env.ui).to receive(:ask).and_return('username') - expect(machine.env.ui).to receive(:ask).and_return('password') + expect(machine.env.ui).to receive(:ask).with(/name/, any_args).and_return('username').at_least(1) + expect(machine.env.ui).to receive(:ask).with(/word/, any_args).and_return('password').at_least(1) end it "should prompt for credentials" do @@ -91,6 +91,27 @@ describe VagrantPlugins::SyncedFolderSMB::SyncedFolder do expect(host).to receive(:capability).with(:smb_start, any_args) subject.prepare(machine, folders, options) end + + context "with host smb_validate_password capability" do + let(:host_caps){ [:smb_start, :smb_prepare, :smb_validate_password] } + + it "should validate the password" do + expect(host).to receive(:capability).with(:smb_validate_password, 'username', 'password').and_return(true) + subject.prepare(machine, folders, options) + end + + it "should retry when validation fails" do + expect(host).to receive(:capability).with(:smb_validate_password, 'username', 'password').and_return(false) + expect(host).to receive(:capability).with(:smb_validate_password, 'username', 'password').and_return(true) + subject.prepare(machine, folders, options) + end + + it "should raise an error if it exceeds the maximum number of retries" do + expect(host).to receive(:capability).with(:smb_validate_password, 'username', 'password').and_return(false). + exactly(VagrantPlugins::SyncedFolderSMB::SyncedFolder::CREDENTIAL_RETRY_MAX).times + expect{ subject.prepare(machine, folders, options) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::CredentialsRequestError) + end + end end context "with credentials provided" do