diff --git a/CHANGELOG.md b/CHANGELOG.md index e6aa5f9ca..a9dfddb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped Stale task to v5 in the GitHub workflow. - Wiki - add introduction and links to DSC technology +- SqlLogin + - BREAKING CHANGE: `LoginMustChangePassword`, `LoginPasswordExpirationEnabled` and `LoginPasswordPolicyEnforced` + parameters no longer enforce default values ([issue #1669](https://github.com/dsccommunity/SqlServerDsc/issues/1669)). ### Fixed diff --git a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 index e57c7461a..ca7739aed 100644 --- a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 +++ b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.psm1 @@ -107,13 +107,13 @@ 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. Does not update pre-existing 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. .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. + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. .PARAMETER LoginPasswordPolicyEnforced - 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 is $true. + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. .PARAMETER Disabled Specifies if the login is disabled. Default is $false. @@ -163,15 +163,15 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $LoginMustChangePassword = $true, + $LoginMustChangePassword, [Parameter()] [System.Boolean] - $LoginPasswordExpirationEnabled = $true, + $LoginPasswordExpirationEnabled, [Parameter()] [System.Boolean] - $LoginPasswordPolicyEnforced = $true, + $LoginPasswordPolicyEnforced, [Parameter()] [System.Boolean] @@ -195,25 +195,34 @@ function Set-TargetResource if ( $login.LoginType -eq 'SqlLogin' ) { # 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 ) + if ( $PSBoundParameters.ContainsKey('LoginMustChangePassword') -and $login.MustChangePassword -ne $LoginMustChangePassword ) { $errorMessage = $script:localizedData.MustChangePasswordCannotBeChanged New-InvalidOperationException -Message $errorMessage } - # `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 ) + # Update SQL login data if either `PasswordPolicyEnforced or `PasswordExpirationEnabled` is specified and not in desired state. + # Avoids executing `Update-SQLServerLogin` twice if both are not in desired state. + if ( ( $PSBoundParameters.ContainsKey('LoginPasswordPolicyEnforced') -and $login.PasswordPolicyEnforced -ne $LoginPasswordPolicyEnforced ) -or + ( $PSBoundParameters.ContainsKey('LoginPasswordExpirationEnabled') -and $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 - ) + if ( $PSBoundParameters.ContainsKey('LoginPasswordPolicyEnforced') ) + { + Write-Verbose -Message ( + $script:localizedData.SetPasswordPolicyEnforced -f $LoginPasswordPolicyEnforced, $Name, $ServerName, $InstanceName + ) - $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced - $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + } + + if ( $PSBoundParameters.ContainsKey('LoginPasswordExpirationEnabled') ) + { + Write-Verbose -Message ( + $script:localizedData.SetPasswordExpirationEnabled -f $LoginPasswordExpirationEnabled, $Name, $ServerName, $InstanceName + ) + + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + } Update-SQLServerLogin -Login $login } @@ -365,13 +374,13 @@ function Set-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. .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. + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. .PARAMETER LoginPasswordPolicyEnforced - 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 is $true. + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. .PARAMETER Disabled Specifies if the login is disabled. Default is $false. @@ -421,15 +430,15 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $LoginMustChangePassword = $true, + $LoginMustChangePassword, [Parameter()] [System.Boolean] - $LoginPasswordExpirationEnabled = $true, + $LoginPasswordExpirationEnabled, [Parameter()] [System.Boolean] - $LoginPasswordPolicyEnforced = $true, + $LoginPasswordPolicyEnforced, [Parameter()] [System.Boolean] @@ -504,7 +513,7 @@ function Test-TargetResource if ( $LoginType -eq 'SqlLogin' ) { - if ( $LoginPasswordExpirationEnabled -ne $loginInfo.LoginPasswordExpirationEnabled ) + if ( $PSBoundParameters.ContainsKey('LoginPasswordExpirationEnabled') -and $LoginPasswordExpirationEnabled -ne $loginInfo.LoginPasswordExpirationEnabled ) { if ($LoginPasswordExpirationEnabled) { @@ -522,7 +531,7 @@ function Test-TargetResource $testPassed = $false } - if ( $LoginPasswordPolicyEnforced -ne $loginInfo.LoginPasswordPolicyEnforced ) + if ( $PSBoundParameters.ContainsKey('LoginPasswordPolicyEnforced') -and $LoginPasswordPolicyEnforced -ne $loginInfo.LoginPasswordPolicyEnforced ) { if ($LoginPasswordPolicyEnforced) { @@ -831,4 +840,3 @@ function Set-SQLServerLoginPassword $ErrorActionPreference = $originalErrorActionPreference } } - diff --git a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof index 5ed542970..8f3c01e23 100644 --- a/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof +++ b/source/DSCResources/DSC_SqlLogin/DSC_SqlLogin.schema.mof @@ -9,9 +9,9 @@ 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`. 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 required to have its password change on the next login. Only applies to _SQL Logins_. 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_.")] 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_.")] Boolean LoginPasswordPolicyEnforced; [Write, Description("Specifies if the login is disabled. Default value is `$false`.")] Boolean Disabled; [Write, Description("Specifies the default database name.")] String DefaultDatabase; }; diff --git a/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 b/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 index 05b5a6ca9..d9003d78d 100644 --- a/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlLogin.Integration.Tests.ps1 @@ -265,8 +265,8 @@ try $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 + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $true } It 'Should return $true when Test-DscConfiguration is run' { @@ -365,8 +365,8 @@ try $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 + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $false } It 'Should return $true when Test-DscConfiguration is run' { @@ -421,6 +421,110 @@ try } + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_UpdateLoginDscUser4_Config_LoginPasswordPolicyEnforced" + + 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 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.LoginPasswordPolicyEnforced | Should -BeTrue + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_UpdateLoginDscUser4_Config_LoginPasswordExpirationEnabled" + + 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 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.LoginPasswordPolicyEnforced | Should -BeTrue + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + Wait-ForIdleLcm -Clear <# @@ -482,6 +586,106 @@ try Wait-ForIdleLcm -Clear + $configurationName = "$($script:dscResourceName)_AddLoginDscUser5_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.DscUser5Name + $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type + $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.LoginMustChangePassword | Should -Be $false + $resourceCurrentState.LoginPasswordPolicyEnforced | Should -Be $false + $resourceCurrentState.LoginPasswordExpirationEnabled | 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 password to connect to SQL Instance (using SqlConnection.Open())' { + $serverName = $ConfigurationData.AllNodes.ServerName + $instanceName = $ConfigurationData.AllNodes.InstanceName + $databaseName = $ConfigurationData.AllNodes.DefaultDbName + $userName = $ConfigurationData.AllNodes.DscUser5Name + $password = $ConfigurationData.AllNodes.DscUser5Pass + + $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.DscUser5Name + $password = $ConfigurationData.AllNodes.DscUser5Pass + + $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)_AddLoginDscSqlUsers1_Config" Context ('When using configuration {0}' -f $configurationName) { diff --git a/tests/Integration/DSC_SqlLogin.config.ps1 b/tests/Integration/DSC_SqlLogin.config.ps1 index eba41fb80..450d4f506 100644 --- a/tests/Integration/DSC_SqlLogin.config.ps1 +++ b/tests/Integration/DSC_SqlLogin.config.ps1 @@ -41,6 +41,11 @@ else DscUser4Type = 'SqlLogin' DscUser4Role = 'sysadmin' + DscUser5Name = 'DscUser5' + DscUser5Pass = 'P@ssw0rd1' + DscUser5Type = 'SqlLogin' + DscUser5Role = 'sysadmin' + DscSqlUsers1Name = ('{0}\{1}' -f $env:COMPUTERNAME, 'DscSqlUsers1') DscSqlUsers1Type = 'WindowsGroup' @@ -310,6 +315,131 @@ Configuration DSC_SqlLogin_UpdateLoginDscUser4_Config } } +<# + .SYNOPSIS + Updates a SQL login, sets LoginPasswordPolicyEnforced to $true +#> +Configuration DSC_SqlLogin_UpdateLoginDscUser4_Config_LoginPasswordPolicyEnforced +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlLogin 'Integration_Test' + { + Ensure = 'Present' + Name = $Node.DscUser4Name + LoginType = $Node.DscUser4Type + LoginPasswordPolicyEnforced = $true + + 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)) + } + } +} + +<# + .SYNOPSIS + Updates a SQL login, sets LoginPasswordExpirationEnabled to $true +#> +Configuration DSC_SqlLogin_UpdateLoginDscUser4_Config_LoginPasswordExpirationEnabled +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlLogin 'Integration_Test' + { + Ensure = 'Present' + Name = $Node.DscUser4Name + LoginType = $Node.DscUser4Type + LoginPasswordExpirationEnabled = $true + + 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)) + } + } +} + +<# + .SYNOPSIS + Adds a SQL login with default password parameters. +#> +Configuration DSC_SqlLogin_AddLoginDscUser5_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlLogin 'Integration_Test' + { + Ensure = 'Present' + Name = $Node.DscUser5Name + LoginType = $Node.DscUser5Type + + LoginCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.DscUser5Name, (ConvertTo-SecureString -String $Node.DscUser5Pass -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.DscUser5Name + UserType = 'Login' + LoginName = $Node.DscUser5Name + + 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.DscUser5Role + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + MembersToInclude = @( + $Node.DscUser5Name + ) + + 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 Adds a Windows Group login. diff --git a/tests/Unit/DSC_SqlLogin.Tests.ps1 b/tests/Unit/DSC_SqlLogin.Tests.ps1 index 36fb8527e..a5bbc72ab 100644 --- a/tests/Unit/DSC_SqlLogin.Tests.ps1 +++ b/tests/Unit/DSC_SqlLogin.Tests.ps1 @@ -221,6 +221,24 @@ try return $mock } + $mockConnectSQL_SQLLogin_PasswordFlagsFalse = { + $sqlLogin = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList @('Server', 'SqlLogin1') + $sqlLogin.LoginType = 'SqlLogin' + $sqlLogin.MustChangePassword = $false + $sqlLogin.DefaultDatabase = 'master' + $sqlLogin.PasswordPolicyEnforced = $false + $sqlLogin.PasswordExpirationEnabled = $false + + $mock = New-Object -TypeName PSObject -Property @{ + LoginMode = 'Mixed' + Logins = @{ + $sqlLogin.Name = $sqlLogin + } + } + + return $mock + } + $mockConnectSQL_LoginMode = { return New-Object -TypeName Object | Add-Member -MemberType ScriptProperty -Name Logins -Value { @@ -268,6 +286,17 @@ try Context 'When the login is Absent' { + It 'Should be Absent when an unknown SQL Login is provided using default server name' { + $getTargetResource_UnknownSqlLogin_DefaultServer = $getTargetResource_UnknownSqlLogin.Clone() + $getTargetResource_UnknownSqlLogin_DefaultServer.Remove('ServerName') + + ( Get-TargetResource @getTargetResource_UnknownSqlLogin_DefaultServer ).Ensure | Should -Be 'Absent' + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -Scope It -Times 1 -Exactly + } + It 'Should be Absent when an unknown SQL Login is provided' { ( Get-TargetResource @getTargetResource_UnknownSqlLogin ).Ensure | Should -Be 'Absent' @@ -282,6 +311,24 @@ try } Context 'When the login is Present' { + It 'Should be Present when a known SQL Login is provided using default server name' { + $getTargetResource_KnownSqlLogin_DefaultServer = $getTargetResource_KnownSqlLogin.Clone() + $getTargetResource_KnownSqlLogin_DefaultServer.Remove('ServerName') + + $result = Get-TargetResource @getTargetResource_KnownSqlLogin_DefaultServer + + $result.Ensure | Should -Be 'Present' + $result.LoginType | Should -Be 'SqlLogin' + $result.DefaultDatabase | Should -Be 'master' + $result.LoginMustChangePassword | Should -Not -BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should -Not -BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should -Not -BeNullOrEmpty + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -Scope It -Times 1 -Exactly + } + It 'Should be Present when a known SQL Login is provided' { $result = Get-TargetResource @getTargetResource_KnownSqlLogin @@ -343,6 +390,18 @@ try Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Context 'When the desired state is Absent' { + It 'Should return $true when the specified Windows user is Absent' { + $testTargetResource_WindowsUserAbsent_EnsureAbsent = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsureAbsent[ 'Ensure' ] = 'Absent' + $testTargetResource_WindowsUserAbsent_EnsureAbsent.Remove('ServerName') + + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsureAbsent ) | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -Scope It -Times 1 -Exactly + } + It 'Should return $true when the specified Windows user is Absent' { $testTargetResource_WindowsUserAbsent_EnsureAbsent = $testTargetResource_WindowsUserAbsent.Clone() $testTargetResource_WindowsUserAbsent_EnsureAbsent[ 'Ensure' ] = 'Absent' @@ -526,6 +585,18 @@ try } Context 'When the desired state is Present' { + It 'Should return $false when the specified Windows user is Absent using default server name' { + $testTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer[ 'Ensure' ] = 'Present' + $testTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer.Remove('ServerName') + + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer ) | Should -Be $false + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -Scope It -Times 1 -Exactly + } + It 'Should return $false when the specified Windows user is Absent' { $testTargetResource_WindowsUserAbsent_EnsurePresent = $testTargetResource_WindowsUserAbsent.Clone() $testTargetResource_WindowsUserAbsent_EnsurePresent[ 'Ensure' ] = 'Present' @@ -633,42 +704,92 @@ try Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return $true when the specified SQL Login is Present and PasswordExpirationEnabled is $true' { - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent[ 'Ensure' ] = 'Present' - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $true + It 'Should return $true when the specified SQL Login is Present and LoginPasswordExpirationEnabled is $true' { + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $true + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent ) | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present and LoginPasswordExpirationEnabled is $false' { + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $false - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent ) | Should -Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent ) | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return $false when the specified SQL Login is Present and PasswordExpirationEnabled is $false' { - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent[ 'Ensure' ] = 'Present' - $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $false + It 'Should return $false when the specified SQL Login is Present, PasswordExpirationEnabled is $false and LoginPasswordExpirationEnabled is $true' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_SQLLogin_PasswordFlagsFalse -Verifiable - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent ) | Should -Be $false + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $true + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledTrue_EnsurePresent ) | Should -Be $false Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return $true when the specified SQL Login is Present and PasswordPolicyEnforced is $true' { - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent[ 'Ensure' ] = 'Present' - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $true + It 'Should return $true when the specified SQL Login is Present, PasswordExpirationEnabled is $false and LoginPasswordExpirationEnabled is $false' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_SQLLogin_PasswordFlagsFalse -Verifiable + + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent[ 'LoginPasswordExpirationEnabled' ] = $false - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent ) | Should -Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordExpirationEnabledFalse_EnsurePresent ) | Should -Be $true Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should return $false when the specified SQL Login is Present and PasswordPolicyEnforced is $false' { - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent[ 'Ensure' ] = 'Present' - $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $false + It 'Should return $true when the specified SQL Login is Present and LoginPasswordPolicyEnforced is $true' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $true - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent ) | Should -Be $false + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent ) | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present and LoginPasswordPolicyEnforced is $false' { + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $false + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent ) | Should -Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present, LoginPasswordPolicyEnforced is $false and LoginPasswordPolicyEnforced is $true' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_SQLLogin_PasswordFlagsFalse -Verifiable + + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $true + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedTrue_EnsurePresent ) | Should -Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $true when the specified SQL Login is Present, LoginPasswordPolicyEnforced is $false and LoginPasswordPolicyEnforced is $false' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_SQLLogin_PasswordFlagsFalse -Verifiable + + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent[ 'Ensure' ] = 'Present' + $testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent[ 'LoginPasswordPolicyEnforced' ] = $false + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithLoginPasswordPolicyEnforcedFalse_EnsurePresent ) | Should -Be $true Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } @@ -696,6 +817,8 @@ try } It 'Should be return $true when a login is enabled' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + $mockTestTargetResourceParameters = $instanceParameters.Clone() $mockTestTargetResourceParameters[ 'Ensure' ] = 'Present' $mockTestTargetResourceParameters[ 'Name' ] = 'Windows\User1' @@ -750,6 +873,24 @@ try $script:mockWasLoginClassMethodDisabledCalled = $false } + It 'Should drop the specified Windows User when it is Present using default server name' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $setTargetResource_WindowsUserPresent_EnsureAbsent_DefaultServer = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsureAbsent_DefaultServer[ 'Ensure' ] = 'Absent' + $setTargetResource_WindowsUserPresent_EnsureAbsent_DefaultServer.Remove('ServerName') + + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsureAbsent_DefaultServer + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -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 1 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + It 'Should drop the specified Windows User when it is Present' { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable @@ -885,6 +1026,24 @@ try $script:mockWasLoginClassMethodDisabledCalled = $false } + It 'Should add the specified Windows User when it is Absent using default server name' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $setTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer[ 'Ensure' ] = 'Present' + $setTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer.Remove('ServerName') + + Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsurePresent_DefaultServer + + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $ServerName -eq (Get-ComputerName) + } -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + It 'Should add the specified Windows User when it is Absent' { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable @@ -900,6 +1059,22 @@ try Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } + It 'Should add the specified Windows User when it is Absent and set the default database' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $setTargetResource_WindowsUserAbsent_EnsurePresent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsurePresent[ 'Ensure' ] = 'Present' + $setTargetResource_WindowsUserAbsent_EnsurePresent[ 'DefaultDatabase' ] = 'notmaster' + + Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsurePresent + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + It 'Should add the specified Windows User as disabled when it is Absent' { Mock -CommandName Connect-SQL -MockWith { return New-Object -TypeName PSObject -Property @{ @@ -981,6 +1156,23 @@ try Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } + It 'Should add the specified SQL Login when it is Absent and MustChangePassword is $true' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent[ 'Ensure' ] = 'Present' + $setTargetResource_SqlLoginAbsent_EnsurePresent[ 'LoginCredential' ] = $mockSqlLoginCredential + $setTargetResource_SqlLoginAbsent_EnsurePresent[ 'LoginMustChangePassword' ] = $true + + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + + 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 1 -Exactly + 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 adding an unsupported login type' { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable