diff --git a/CHANGELOG.md b/CHANGELOG.md index 383be9eb9..b521a8550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- SqlLogin + - Added functionality to throw exception if an update to the `LoginMustChangePassword` + value on an existing SQL Login is attempted. This functionality is not supported + by referenced, SQL Server Management Object (SMO), libraries and cannot be + supported directly by this module. + - Added integration tests to ensure that an added (or updated) `SqlLogin` can + connect into a SQL instance once added (or updated). + - Added integration tests to ensure that the default database connected to by + a `SqlLogin` is the same as specified in the resource's `DefaultDatabase` + property/parameter. + - Reversed order in which `PasswordExpirationEnabled` and `PasswordPolicyEnforced` + are updated within `SqlLogin` resource. `PasswordPolicyEnforced` is now updated + first. + +### Fixed + +- SqlLogin + - Added integration tests to assert `LoginPasswordExpirationEnabled`, + `LoginPasswordPolicyEnforced` and `LoginMustChangePassword` properties/parameters + are applied and updated correctly. Similar integration tests also added to ensure + the password of the `SqlLogin` is updated if the password within the `SqlCredential` + value/object is changed ([issue #361](https://github.com/dsccommunity/SqlServerDsc/issues/361), + [issue #1032](https://github.com/dsccommunity/SqlServerDsc/issues/1032) and + [issue #1050](https://github.com/dsccommunity/SqlServerDsc/issues/1050)). + - Updated `SqlLogin`, integration tests to make use of amended `Wait-ForIdleLcm`, + helper function, `-Clear` switch usage to remove intermittent, integration + test failures ([issue #1634](https://github.com/dsccommunity/SqlServerDsc/issues/1634)). + ## [15.0.1] - 2021-01-09 ### Changed diff --git a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 index 04dc9624c..e57c7461a 100644 --- a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 +++ b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 @@ -107,7 +107,7 @@ function Get-TargetResource The credential containing the password for a SQL Login. Only applies if the login type is SqlLogin. .PARAMETER LoginMustChangePassword - Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. + Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Does not update pre-existing SQL Logins. Default is $true. .PARAMETER LoginPasswordExpirationEnabled Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. @@ -194,23 +194,27 @@ function Set-TargetResource if ( $login.LoginType -eq 'SqlLogin' ) { - if ( $login.PasswordExpirationEnabled -ne $LoginPasswordExpirationEnabled ) + # There is no way to update 'MustChangePassword' on existing login so must explicitly throw exception to avoid this functionality being assumed + if ( $login.MustChangePassword -ne $LoginMustChangePassword ) { - Write-Verbose -Message ( - $script:localizedData.SetPasswordExpirationEnabled -f $LoginPasswordExpirationEnabled, $Name, $ServerName, $InstanceName - ) - - $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled - Update-SQLServerLogin -Login $login + $errorMessage = $script:localizedData.MustChangePasswordCannotBeChanged + New-InvalidOperationException -Message $errorMessage } - if ( $login.PasswordPolicyEnforced -ne $LoginPasswordPolicyEnforced ) + # `PasswordPolicyEnforced and `PasswordExpirationEnabled` must be updated together (if one or both are not in the desired state) + if ( $login.PasswordPolicyEnforced -ne $LoginPasswordPolicyEnforced -or + $login.PasswordExpirationEnabled -ne $LoginPasswordExpirationEnabled ) { Write-Verbose -Message ( $script:localizedData.SetPasswordPolicyEnforced -f $LoginPasswordPolicyEnforced, $Name, $ServerName, $InstanceName ) + Write-Verbose -Message ( + $script:localizedData.SetPasswordExpirationEnabled -f $LoginPasswordExpirationEnabled, $Name, $ServerName, $InstanceName + ) $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + Update-SQLServerLogin -Login $login } diff --git a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof index 6afa997af..5ed542970 100644 --- a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof +++ b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof @@ -9,7 +9,7 @@ class DSC_SqlLogin : OMI_BaseResource Values{"WindowsUser","WindowsGroup","SqlLogin","Certificate","AsymmetricKey","ExternalUser","ExternalGroup"}] String LoginType; [Write, Description("The hostname of the _SQL Server_ to be configured. Default value is the current computer name.")] String ServerName; [Write, EmbeddedInstance("MSFT_Credential"), Description("Specifies the password as a `[PSCredential]` object. Only applies to _SQL Logins_.")] String LoginCredential; - [Write, Description("Specifies if the login is required to have its password change on the next login. Only applies to _SQL Logins_. Default value is `$true`.")] Boolean LoginMustChangePassword; + [Write, Description("Specifies if the login is required to have its password change on the next login. Only applies to _SQL Logins_. Default value is `$true`. This cannot be updated on a pre-existing _SQL Login_ and any attempt to do this will throw an exception.")] Boolean LoginMustChangePassword; [Write, Description("Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to _SQL Logins_. Default value is `$true`.")] Boolean LoginPasswordExpirationEnabled; [Write, Description("Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to _SQL Logins_. Default value is `$true`.")] Boolean LoginPasswordPolicyEnforced; [Write, Description("Specifies if the login is disabled. Default value is `$false`.")] Boolean Disabled; diff --git a/source/DSCResources/DSC_SqlLogin/README.md b/source/DSCResources/DSC_SqlLogin/README.md index 4e0bb9ea6..982a608e8 100644 --- a/source/DSCResources/DSC_SqlLogin/README.md +++ b/source/DSCResources/DSC_SqlLogin/README.md @@ -7,9 +7,17 @@ for a SQL Server instance. * Target machine must be running Windows Server 2012 or later. * Target machine must be running SQL Server Database Engine 2012 or later. -* When the `LoginType` `'SqlLogin'` is used, then the login authentication +* When the `LoginType` of `'SqlLogin'` is used, then the login authentication mode must have been set to `Mixed` or `Normal`. If set to `Integrated` and error will be thrown. +* The `LoginMustChangePassword` property/parameter is only valid on a `SqlLogin` + where the `LoginType` property/parameter is set to `'SqlLogin'`. +* The `LoginMustChangePassword` property/parameter can **not** be used to change + this setting on a pre-existing `SqlLogin` - This property/parameter can only + be used when creating a new `SqlLogin` and where subsequent updates will + not be applied or, alternatively, when the desired state will not change (for example, + where `LoginMustChangePassword` is initially set to `$false` and will always + be set to `$false`). ## Known issues diff --git a/source/DSCResources/DSC_SqlLogin/en-US/DSC_SqlLogin.strings.psd1 b/source/DSCResources/DSC_SqlLogin/en-US/DSC_SqlLogin.strings.psd1 index f11465e91..7cb16204d 100644 --- a/source/DSCResources/DSC_SqlLogin/en-US/DSC_SqlLogin.strings.psd1 +++ b/source/DSCResources/DSC_SqlLogin/en-US/DSC_SqlLogin.strings.psd1 @@ -33,4 +33,5 @@ ConvertFrom-StringData @' DropLoginFailed = Removal of the login '{0}' failed. SetPasswordValidationFailed = Setting the password failed for the login '{0}' because of password validation error. SetPasswordFailed = Setting the password failed for the login '{0}'. + MustChangePasswordCannotBeChanged = The '(Login)MustChangePassword' parameter cannot be updated on a login that is already present. '@ diff --git a/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 b/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 index 0369d1df6..cd5ea7c68 100644 --- a/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 @@ -66,7 +66,7 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_AddLoginDscUser1_Config" @@ -117,7 +117,7 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_AddLoginDscUser2_Config" @@ -169,7 +169,7 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_AddLoginDscUser3_Disabled_Config" @@ -220,7 +220,7 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_AddLoginDscUser4_Config" @@ -264,14 +264,223 @@ try $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser4Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser4Type $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.LoginMustChangePassword | Should -Be $false + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $true + $resourceCurrentState.LoginPasswordPolicyEnforced | Should -Be $true } It 'Should return $true when Test-DscConfiguration is run' { Test-DscConfiguration -Verbose | Should -Be 'True' } + + It 'Should allow SQL Server, login username and password to connect to SQL Instance (using SqlConnection.Open())' { + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $databaseName = $ConfigurationData.AllNodes.DefaultDbName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass1 # Original password + + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;Database={4};' -f $serverName, $instanceName, $userName, $password, $databaseName + + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlConnection.Open() + $sqlConnection.Close() + } | Should -Not -Throw + } + + It 'Should allow SQL Server, login username and password to connect to correct, SQL instance, default database' { + $script:CurrentDatabaseName = $null + + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass1 # Original password + + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;' -f $serverName, $instanceName, $userName, $password # Note: Not providing a database name + + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand('SELECT DB_NAME() as CurrentDatabaseName', $sqlConnection) + + $sqlConnection.Open() + $sqlDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $sqlCommand + $sqlDataSet = New-Object System.Data.DataSet + $sqlDataAdapter.Fill($sqlDataSet) | Out-Null + $sqlConnection.Close() + + $sqlDataSet.Tables[0].Rows[0].CurrentDatabaseName | Should -Be $ConfigurationData.AllNodes.DefaultDbName + + $script:CurrentDatabaseName = $sqlDataSet.Tables[0].Rows[0].CurrentDatabaseName + } | Should -Not -Throw + + $script:CurrentDatabaseName | Should -Be $ConfigurationData.AllNodes.DefaultDbName + + $script:CurrentDatabaseName = $null + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_UpdateLoginDscUser4_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser4Name + $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser4Type + $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.LoginMustChangePassword | Should -Be $false # Left the same as this cannot be updated + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $false + $resourceCurrentState.LoginPasswordPolicyEnforced | Should -Be $false + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + + It 'Should allow SQL Server, login username and (changed) password to connect to SQL Instance (using SqlConnection.Open())' { + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $databaseName = $ConfigurationData.AllNodes.DefaultDbName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass2 # Changed password + + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;Database={4};' -f $serverName, $instanceName, $userName, $password, $databaseName + + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlConnection.Open() + $sqlConnection.Close() + } | Should -Not -Throw + } + + It 'Should allow SQL Server, login username and (changed) password to connect to correct, SQL instance, default database' { + $script:CurrentDatabaseName = $null + + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass2 # Changed password + + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;' -f $serverName, $instanceName, $userName, $password # Note: Not providing a database name + + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand('SELECT DB_NAME() as CurrentDatabaseName', $sqlConnection) + + $sqlConnection.Open() + $sqlDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $sqlCommand + $sqlDataSet = New-Object System.Data.DataSet + $sqlDataAdapter.Fill($sqlDataSet) | Out-Null + $sqlConnection.Close() + + $sqlDataSet.Tables[0].Rows[0].CurrentDatabaseName | Should -Be $ConfigurationData.AllNodes.DefaultDbName + + $script:CurrentDatabaseName = $sqlDataSet.Tables[0].Rows[0].CurrentDatabaseName + } | Should -Not -Throw + + $script:CurrentDatabaseName | Should -Be $ConfigurationData.AllNodes.DefaultDbName + + $script:CurrentDatabaseName = $null + } + } + + + Wait-ForIdleLcm -Clear + + <# + Note that this configuration has already been run within these Integration tests but is + executed once more to reset the password back to the original one provided. + #> + $configurationName = "$($script:dscResourceName)_AddLoginDscUser4_Config" + + Context ('When using configuration {0} (to update back to original password)' -f $configurationName) { + It 'Should re-compile and re-apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + + It 'Should allow SQL Server, login username and password to connect to SQL Instance (using SqlConnection.Open())' { + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $databaseName = $ConfigurationData.AllNodes.DefaultDbName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass1 # Original password + + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;Database={4};' -f $serverName, $instanceName, $userName, $password, $databaseName + + { + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlConnection.Open() + $sqlConnection.Close() + } | Should -Not -Throw + } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_AddLoginDscSqlUsers1_Config" @@ -322,7 +531,7 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_RemoveLoginDscUser3_Config" @@ -372,11 +581,38 @@ try } } - Wait-ForIdleLcm + Wait-ForIdleLcm -Clear + + Context 'When preparing database, dependencies cleanup' { + + # Details used for database, dependency cleanup/preparation + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $userName = $ConfigurationData.AllNodes.DscUser4Name + $password = $ConfigurationData.AllNodes.DscUser4Pass1 # Using original password + $defaultDbName = $ConfigurationData.AllNodes.DefaultDbName + + It ('Should be able to take the "{0}" database offline without throwing' -f $defaultDbName) { + + { + # Take database offline (closing any existing connections and transactions) before it is dropped in subsequent, 'CleanupDependencies' configuration/test + $sqlConnectionString = 'Data Source={0}\{1};User ID={2};Password={3};Connect Timeout=5;Database=master;' -f $serverName, $instanceName, $userName, $password + $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString + $sqlStatement = 'ALTER DATABASE [{0}] SET OFFLINE WITH ROLLBACK IMMEDIATE' -f $defaultDbName + $sqlCommand = New-Object System.Data.SqlClient.SqlCommand($sqlStatement, $sqlConnection) + $sqlConnection.Open() + $sqlCommand.ExecuteNonQuery() + $sqlConnection.Close() + } | Should -Not -Throw + } + } + + Wait-ForIdleLcm -Clear $configurationName = "$($script:dscResourceName)_CleanupDependencies_Config" Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { { $configurationParameters = @{ diff --git a/tests/Integration/DSC_SqlLogin.config.ps1 b/tests/Integration/DSC_SqlLogin.config.ps1 index e9b692de6..eba41fb80 100644 --- a/tests/Integration/DSC_SqlLogin.config.ps1 +++ b/tests/Integration/DSC_SqlLogin.config.ps1 @@ -36,7 +36,10 @@ else DscUser3Type = 'WindowsUser' DscUser4Name = 'DscUser4' + DscUser4Pass1 = 'P@ssw0rd1' + DscUser4Pass2 = 'P@ssw0rd2' DscUser4Type = 'SqlLogin' + DscUser4Role = 'sysadmin' DscSqlUsers1Name = ('{0}\{1}' -f $env:COMPUTERNAME, 'DscSqlUsers1') DscSqlUsers1Type = 'WindowsGroup' @@ -215,9 +218,87 @@ Configuration DSC_SqlLogin_AddLoginDscUser4_Config LoginMustChangePassword = $false LoginPasswordExpirationEnabled = $true LoginPasswordPolicyEnforced = $true + LoginCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.DscUser4Name, (ConvertTo-SecureString -String $Node.DscUser4Pass1 -AsPlainText -Force)) + + DefaultDatabase = $Node.DefaultDbName + + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + + PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Admin_UserName, (ConvertTo-SecureString -String $Node.Admin_Password -AsPlainText -Force)) + } + + # Database user is also added so the connection into database (using the login) can be tested + SqlDatabaseUser 'Integration_Test_DatabaseUser' + { + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + + DatabaseName = $Node.DefaultDbName + Name = $Node.DscUser4Name + UserType = 'Login' + LoginName = $Node.DscUser4Name + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Admin_UserName, (ConvertTo-SecureString -String $Node.Admin_Password -AsPlainText -Force)) + + DependsOn = @( + '[SqlLogin]Integration_Test' + ) + } + + SqlRole 'Integration_Test_SqlRole' + { + Ensure = 'Present' + ServerRoleName = $Node.DscUser4Role + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + MembersToInclude = @( + $Node.DscUser4Name + ) + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Admin_UserName, (ConvertTo-SecureString -String $Node.Admin_Password -AsPlainText -Force)) + + DependsOn = @( + '[SqlDatabaseUser]Integration_Test_DatabaseUser' + ) + } + } +} + +<# + .SYNOPSIS + Updates a SQL login. +#> +Configuration DSC_SqlLogin_UpdateLoginDscUser4_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlLogin 'Integration_Test' + { + Ensure = 'Present' + Name = $Node.DscUser4Name + LoginType = $Node.DscUser4Type + LoginMustChangePassword = $false # Left the same as this cannot be updated + LoginPasswordExpirationEnabled = $false + LoginPasswordPolicyEnforced = $false + + # Note: This login uses the 'DscUser4Pass2' property value (and not the 'DscUser4Pass1' property value) to validate/test a password change + LoginCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.DscUser4Name, (ConvertTo-SecureString -String $Node.DscUser4Pass2 -AsPlainText -Force)) + + DefaultDatabase = $Node.DefaultDbName ServerName = $Node.ServerName InstanceName = $Node.InstanceName diff --git a/tests/Integration/README.md b/tests/Integration/README.md index 348b4fe5c..228026eea 100644 --- a/tests/Integration/README.md +++ b/tests/Integration/README.md @@ -202,9 +202,14 @@ Login | Type | Password | Permission DscUser4 | SQL | P@ssw0rd1 | *None* `$env:COMPUTERNAME`\DscSqlUsers1 | Windows Group | -- | *None* + >**Note:** The `$env:COMPUTERNAME` is reference to the build workers computer >name. The SQL login could for example be 'APPVYR-WIN\DscUser1'. +>**Note:** The password for `DscUser4` is changed to `P@ssw0rd2` during one of the +>`SqlLogin`, integration tests and then set back again in a subsequent test. + + ## SqlAgentAlert **Run order:** 2 diff --git a/tests/TestHelpers/CommonTestHelper.psm1 b/tests/TestHelpers/CommonTestHelper.psm1 index b5e58eb42..b7a42d1bf 100644 --- a/tests/TestHelpers/CommonTestHelper.psm1 +++ b/tests/TestHelpers/CommonTestHelper.psm1 @@ -388,6 +388,9 @@ function Test-ContinuousIntegrationTaskCategory .SYNOPSIS Waits for LCM to become idle. + .PARAMETER Clear + If specified, the LCM will also be cleared of DSC configurations. + .NOTES Used in integration test where integration tests run to quickly before LCM have time to cool down. @@ -395,7 +398,12 @@ function Test-ContinuousIntegrationTaskCategory function Wait-ForIdleLcm { [CmdletBinding()] - param () + param + ( + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Clear + ) while ((Get-DscLocalConfigurationManager).LCMState -ne 'Idle') { @@ -403,6 +411,11 @@ function Wait-ForIdleLcm Start-Sleep -Seconds 2 } + + if ($Clear) + { + Clear-DscLcmConfiguration + } } <# diff --git a/tests/Unit/DSC_SqlLogin.Tests.ps1 b/tests/Unit/DSC_SqlLogin.Tests.ps1 index f0bb7984d..36fb8527e 100644 --- a/tests/Unit/DSC_SqlLogin.Tests.ps1 +++ b/tests/Unit/DSC_SqlLogin.Tests.ps1 @@ -1049,6 +1049,7 @@ try $setTargetResource_SqlLoginPresent_EnsurePresent = $setTargetResource_SqlLoginPresent.Clone() $setTargetResource_SqlLoginPresent_EnsurePresent[ 'Ensure' ] = 'Present' $setTargetResource_SqlLoginPresent_EnsurePresent[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginPresent_EnsurePresent[ 'LoginMustChangePassword' ] = $false # Stays the same Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent @@ -1065,6 +1066,7 @@ try $setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase = $setTargetResource_SqlLoginPresent.Clone() $setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase[ 'Ensure' ] = 'Present' $setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase[ 'LoginMustChangePassword' ] = $false # Stays the same $setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase[ 'DefaultDatabase' ] = 'notmaster' Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginDefaultDatabase @@ -1079,6 +1081,7 @@ try $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled = $setTargetResource_SqlLoginPresent.Clone() $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled[ 'Ensure' ] = 'Present' $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled[ 'LoginMustChangePassword' ] = $false # Stays the same $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled[ 'LoginPasswordExpirationEnabled' ] = $false Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled @@ -1096,6 +1099,7 @@ try $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced = $setTargetResource_SqlLoginPresent.Clone() $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced[ 'Ensure' ] = 'Present' $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced[ 'LoginMustChangePassword' ] = $false # Stays the same $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced[ 'LoginPasswordPolicyEnforced' ] = $false Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced @@ -1129,6 +1133,25 @@ try Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } + + It 'Should throw the correct error when updating a SQL Login if MustChangePassword is different' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_LoginMode -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent[ 'Ensure' ] = 'Present' + $setTargetResource_SqlLoginPresent_EnsurePresent[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginPresent_EnsurePresent[ 'LoginMustChangePassword' ] = $true + + $errorMessage = $script:localizedData.MustChangePasswordCannotBeChanged + + { Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent } | Should -Throw $errorMessage + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } } It 'Should not throw an error when creating a SQL Login and the LoginMode is set to ''Normal''' {