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/hosts/windows/scripts/check_credentials.ps1 b/plugins/hosts/windows/scripts/check_credentials.ps1 new file mode 100644 index 000000000..28ce28a90 --- /dev/null +++ b/plugins/hosts/windows/scripts/check_credentials.ps1 @@ -0,0 +1,19 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$username, + [Parameter(Mandatory=$true)] + [string]$password +) + +Add-Type -AssemblyName System.DirectoryServices.AccountManagement + +$DSContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( + [System.DirectoryServices.AccountManagement.ContextType]::Machine, + $env:COMPUTERNAME +) + +if ( $DSContext.ValidateCredentials( $username, $password ) ) { + exit 0 +} else { + exit 1 +} \ No newline at end of file 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 426def425..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 @@ -30,11 +34,6 @@ module VagrantPlugins def prepare(machine, folders, opts) machine.ui.output(I18n.t("vagrant_sf_smb.preparing")) - # Check if this host can start and SMB service - if machine.env.host.capability?(:smb_start) - machine.env.host.capability(:smb_start) - end - smb_username = smb_password = nil # If we need auth information, then ask the user. @@ -49,9 +48,32 @@ module VagrantPlugins end if !have_auth - machine.env.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ") - smb_username = machine.env.ui.ask("Username: ") - smb_password = machine.env.ui.ask("Password (will be hidden): ", echo: false) + machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ") + 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 + + 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 + + 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 + + # Check if this host can start and SMB service + if machine.env.host.capability?(:smb_start) + machine.env.host.capability(:smb_start) end folders.each do |id, data| diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml index 3521ff4ff..0dbf7ac1d 100644 --- a/templates/locales/synced_folder_smb.yml +++ b/templates/locales/synced_folder_smb.yml @@ -15,6 +15,8 @@ en: You will be asked for the username and password to use for the SMB folders shortly. Please use the proper username/password of your account. + incorrect_credentials: |- + Credentials incorrect. Please try again. uac: prune_warning: |- @@ -37,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