diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index 42c3e22617..cfbc43a63c 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -14,6 +14,13 @@ applyTo: "**/*.[Tt]ests.ps1" - Never test verbose messages, debug messages or parameter binding behavior - Pass all mandatory parameters to avoid prompts +## Requirements +- Inside `It` blocks, assign unused return objects to `$null` (unless part of pipeline) +- Tested entity must be called from within the `It` blocks +- Keep results and assertions in same `It` block +- Avoid try-catch-finally for cleanup, use `AfterAll` or `AfterEach` +- Avoid unnecessary remove/recreate cycles + ## Naming - One `Describe` block per file matching the tested entity name - `Context` descriptions start with 'When' @@ -51,10 +58,6 @@ applyTo: "**/*.[Tt]ests.ps1" - Keep scope close to usage context ## Best Practices -- Inside `It` blocks, assign unused return objects to `$null` (unless part of pipeline) -- Tested entity must be called from within the `It` blocks -- Keep results and assertions in same `It` block - Cover all scenarios and code paths - Use `BeforeEach` and `AfterEach` sparingly -- Avoid try-catch-finally for cleanup, use `AfterAll` or `AfterEach` -- Avoid unnecessary remove/recreate cycles +- Use `$PSDefaultParameterValues` only for Pester commands (`Describe`, `Context`, `It`, `Mock`, `Should`, `InModuleScope`) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8fca5f19..bb961f34dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhanced workflow with proper environment variable configuration and DSCv3 verification. - Fixed environment variable persistence by using $GITHUB_ENV instead of job-level env declaration. +- `Grant-SqlDscServerPermission` + - Added new public command to grant server permissions to a principal (Login or ServerRole) on a SQL Server Database Engine instance. +- `Deny-SqlDscServerPermission` + - Added new public command to deny server permissions to a principal (Login or ServerRole). +- `Revoke-SqlDscServerPermission` + - Added new public command to revoke server permissions from a principal (Login or ServerRole). +- `Test-SqlDscServerPermission` + - Added new public command with Grant/Deny parameter sets (and `-WithGrant`) to test server permissions for a principal. - `Assert-SqlDscLogin` - Added new public command to validate that a specified SQL Server principal is a login. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ffc76a1ded..14cc5cd786 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -299,18 +299,24 @@ stages: 'tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1' - 'tests/Integration/Commands/Remove-SqlDscRole.Integration.Tests.ps1' - 'tests/Integration/Commands/Remove-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Grant-SqlDscServerPermission.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscServerPermission.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscServerPermission.Integration.Tests.ps1' + 'tests/Integration/Commands/Deny-SqlDscServerPermission.Integration.Tests.ps1' + 'tests/Integration/Commands/Revoke-SqlDscServerPermission.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscDatabase.Integration.Tests.ps1' - 'tests/Integration/Commands/Remove-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1' + # Group 8 'tests/Integration/Commands/Remove-SqlDscAgentAlert.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscDatabase.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscRole.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscLogin.Integration.Tests.ps1' # Group 9 'tests/Integration/Commands/Uninstall-SqlDscServer.Integration.Tests.ps1' diff --git a/source/Enum/002.ServerPermission.ps1 b/source/Enum/002.ServerPermission.ps1 new file mode 100644 index 0000000000..1e439b511a --- /dev/null +++ b/source/Enum/002.ServerPermission.ps1 @@ -0,0 +1,63 @@ +<# + .SYNOPSIS + The possible server permissions that can be granted, denied, or revoked. + + .NOTES + The available permissions can be seen in the ServerPermission Class documentation: + https://learn.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.serverpermission +#> +enum SqlServerPermission +{ + # cSpell:ignore securables + AdministerBulkOperations = 1 + AlterAnyAvailabilityGroup + AlterAnyConnection + AlterAnyCredential + AlterAnyDatabase + AlterAnyEndpoint + AlterAnyEventNotification + AlterAnyEventSession + AlterAnyEventSessionAddEvent + AlterAnyEventSessionAddTarget + AlterAnyEventSessionDisable + AlterAnyEventSessionDropEvent + AlterAnyEventSessionDropTarget + AlterAnyEventSessionEnable + AlterAnyEventSessionOption + AlterAnyLinkedServer + AlterAnyLogin + AlterAnyServerAudit + AlterAnyServerRole + AlterResources + AlterServerState + AlterSettings + AlterTrace + AuthenticateServer + ConnectAnyDatabase + ConnectSql + ControlServer + CreateAnyDatabase + CreateAnyEventSession + CreateAvailabilityGroup + CreateDdlEventNotification + CreateEndpoint + CreateLogin + CreateServerRole + CreateTraceEventNotification + DropAnyEventSession + ExternalAccessAssembly + ImpersonateAnyLogin + SelectAllUserSecurables + Shutdown + UnsafeAssembly + ViewAnyCryptographicallySecuredDefinition + ViewAnyDatabase + ViewAnyDefinition + ViewAnyErrorLog + ViewAnyPerformanceDefinition + ViewAnySecurityDefinition + ViewServerPerformanceState + ViewServerSecurityAudit + ViewServerSecurityState + ViewServerState +} diff --git a/source/Public/Deny-SqlDscServerPermission.ps1 b/source/Public/Deny-SqlDscServerPermission.ps1 new file mode 100644 index 0000000000..e680456043 --- /dev/null +++ b/source/Public/Deny-SqlDscServerPermission.ps1 @@ -0,0 +1,134 @@ +<# + .SYNOPSIS + Denies server permissions to a principal on a SQL Server Database Engine instance. + + .DESCRIPTION + This command denies server permissions to an existing principal on a SQL Server + Database Engine instance. The principal can be specified as either a Login + object (from Get-SqlDscLogin) or a ServerRole object (from Get-SqlDscRole). + + .PARAMETER Login + Specifies the Login object for which the permissions are denied. + This parameter accepts pipeline input. + + .PARAMETER ServerRole + Specifies the ServerRole object for which the permissions are denied. + This parameter accepts pipeline input. + + .PARAMETER Permission + Specifies the permissions to be denied. Specify multiple permissions by + providing an array of SqlServerPermission enum values. + + .PARAMETER Force + Specifies that the permissions should be denied without any confirmation. + + .OUTPUTS + None. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $login = $serverInstance | Get-SqlDscLogin -Name 'MyLogin' + + Deny-SqlDscServerPermission -Login $login -Permission ConnectSql, ViewServerState + + Denies the specified permissions to the login 'MyLogin'. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $role = $serverInstance | Get-SqlDscRole -Name 'MyRole' + + $role | Deny-SqlDscServerPermission -Permission AlterAnyDatabase -Force + + Denies the specified permissions to the role 'MyRole' without prompting for confirmation. + + .NOTES + The Login or ServerRole object must come from the same SQL Server instance + where the permissions will be denied. If specifying `-ErrorAction 'SilentlyContinue'` + then the command will silently continue if any errors occur. If specifying + `-ErrorAction 'Stop'` the command will throw an error on any failure. +#> +function Deny-SqlDscServerPermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Login')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ServerRole')] + [Microsoft.SqlServer.Management.Smo.ServerRole] + $ServerRole, + + [Parameter(Mandatory = $true)] + [SqlServerPermission[]] + $Permission, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + # Determine which principal object we're working with + if ($PSCmdlet.ParameterSetName -eq 'Login') + { + $principalName = $Login.Name + $serverObject = $Login.Parent + } + else + { + $principalName = $ServerRole.Name + $serverObject = $ServerRole.Parent + } + + $verboseDescriptionMessage = $script:localizedData.ServerPermission_Deny_ShouldProcessVerboseDescription -f $principalName, $serverObject.InstanceName, ($Permission -join ',') + $verboseWarningMessage = $script:localizedData.ServerPermission_Deny_ShouldProcessVerboseWarning -f $principalName + $captionMessage = $script:localizedData.ServerPermission_Deny_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + # Convert enum array to ServerPermissionSet object + $permissionSet = [Microsoft.SqlServer.Management.Smo.ServerPermissionSet]::new() + foreach ($permissionName in $Permission) + { + $permissionSet.$permissionName = $true + } + + # Get the permissions names that are set to $true in the ServerPermissionSet. + $permissionName = $permissionSet | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $permissionSet.$_ + } + + try + { + $serverObject.Deny($permissionSet, $principalName) + } + catch + { + $errorMessage = $script:localizedData.ServerPermission_Deny_FailedToDenyPermission -f $principalName, $serverObject.InstanceName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'DSDSP0001', # cSpell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $principalName + ) + ) + } + } + } +} diff --git a/source/Public/Grant-SqlDscServerPermission.ps1 b/source/Public/Grant-SqlDscServerPermission.ps1 new file mode 100644 index 0000000000..2c46e0bb7e --- /dev/null +++ b/source/Public/Grant-SqlDscServerPermission.ps1 @@ -0,0 +1,156 @@ +<# + .SYNOPSIS + Grants server permissions to a principal on a SQL Server Database Engine instance. + + .DESCRIPTION + This command grants server permissions to an existing principal on a SQL Server + Database Engine instance. The principal can be specified as either a Login + object (from Get-SqlDscLogin) or a ServerRole object (from Get-SqlDscRole). + + .PARAMETER Login + Specifies the Login object for which the permissions are granted. + This parameter accepts pipeline input. + + .PARAMETER ServerRole + Specifies the ServerRole object for which the permissions are granted. + This parameter accepts pipeline input. + + .PARAMETER Permission + Specifies the permissions to be granted. Specify multiple permissions by + providing an array of SqlServerPermission enum values. + + .PARAMETER WithGrant + Specifies that the principal should also be granted the right to grant + other principals the same permission. + + .PARAMETER Force + Specifies that the permissions should be granted without any confirmation. + + .OUTPUTS + None. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $login = $serverInstance | Get-SqlDscLogin -Name 'MyLogin' + + Grant-SqlDscServerPermission -Login $login -Permission ConnectSql, ViewServerState + + Grants the specified permissions to the login 'MyLogin'. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $role = $serverInstance | Get-SqlDscRole -Name 'MyRole' + + $role | Grant-SqlDscServerPermission -Permission AlterAnyDatabase -WithGrant -Force + + Grants the specified permissions with grant option to the role 'MyRole' without prompting for confirmation. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + + $serverInstance | Get-SqlDscLogin | Grant-SqlDscServerPermission -Permission ConnectSql + + Grants ConnectSql permission to all logins from the pipeline. + + .NOTES + The Login or ServerRole object must come from the same SQL Server instance + where the permissions will be granted. If specifying `-ErrorAction 'SilentlyContinue'` + then the command will silently continue if any errors occur. If specifying + `-ErrorAction 'Stop'` the command will throw an error on any failure. +#> +function Grant-SqlDscServerPermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Login')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ServerRole')] + [Microsoft.SqlServer.Management.Smo.ServerRole] + $ServerRole, + + [Parameter(Mandatory = $true)] + [SqlServerPermission[]] + $Permission, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithGrant, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + # Determine which principal object we're working with + if ($PSCmdlet.ParameterSetName -eq 'Login') + { + $principalName = $Login.Name + $serverObject = $Login.Parent + } + else + { + $principalName = $ServerRole.Name + $serverObject = $ServerRole.Parent + } + + $verboseDescriptionMessage = $script:localizedData.ServerPermission_Grant_ShouldProcessVerboseDescription -f $principalName, $serverObject.InstanceName, ($Permission -join ',') + $verboseWarningMessage = $script:localizedData.ServerPermission_Grant_ShouldProcessVerboseWarning -f $principalName + $captionMessage = $script:localizedData.ServerPermission_Grant_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + # Convert enum array to ServerPermissionSet object + $permissionSet = [Microsoft.SqlServer.Management.Smo.ServerPermissionSet]::new() + foreach ($permissionName in $Permission) + { + $permissionSet.$permissionName = $true + } + + # Get the permissions names that are set to $true in the ServerPermissionSet. + $permissionName = $permissionSet | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $permissionSet.$_ + } + + try + { + if ($WithGrant.IsPresent) + { + $serverObject.Grant($permissionSet, $principalName, $true) + } + else + { + $serverObject.Grant($permissionSet, $principalName) + } + } + catch + { + $errorMessage = $script:localizedData.ServerPermission_Grant_FailedToGrantPermission -f $principalName, $serverObject.InstanceName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'GSDSP0001', # cSpell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $principalName + ) + ) + } + } + } +} diff --git a/source/Public/Revoke-SqlDscServerPermission.ps1 b/source/Public/Revoke-SqlDscServerPermission.ps1 new file mode 100644 index 0000000000..2cc0f5286b --- /dev/null +++ b/source/Public/Revoke-SqlDscServerPermission.ps1 @@ -0,0 +1,154 @@ +<# + .SYNOPSIS + Removes (revokes) server permissions from a principal on a SQL Server Database Engine instance. + + .DESCRIPTION + This command removes (revokes) server permissions from an existing principal on + a SQL Server Database Engine instance. The principal can be specified as either + a Login object (from Get-SqlDscLogin) or a ServerRole object (from Get-SqlDscRole). + + .PARAMETER Login + Specifies the Login object for which the permissions are revoked. + This parameter accepts pipeline input. + + .PARAMETER ServerRole + Specifies the ServerRole object for which the permissions are revoked. + This parameter accepts pipeline input. + + .PARAMETER Permission + Specifies the permissions to revoke. Specify multiple permissions by + providing an array of SqlServerPermission enum values. + + .PARAMETER WithGrant + Specifies that the right to grant the permission should also be revoked, + and the revocation will cascade to other principals that the grantee has + granted the same permission to. + + .PARAMETER Force + Specifies that the permissions should be revoked without any confirmation. + + .OUTPUTS + None. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $login = $serverInstance | Get-SqlDscLogin -Name 'MyLogin' + + Revoke-SqlDscServerPermission -Login $login -Permission ConnectSql, ViewServerState + + Revokes the specified permissions from the login 'MyLogin'. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $role = $serverInstance | Get-SqlDscRole -Name 'MyRole' + + $role | Revoke-SqlDscServerPermission -Permission AlterAnyDatabase -WithGrant -Force + + Revokes the specified permissions and the right to grant them from the role 'MyRole' with cascading effect, without prompting for confirmation. + + .NOTES + The Login or ServerRole object must come from the same SQL Server instance + where the permissions will be revoked. If specifying `-ErrorAction 'SilentlyContinue'` + then the command will silently continue if any errors occur. If specifying + `-ErrorAction 'Stop'` the command will throw an error on any failure. + + When revoking permission with -WithGrant, both the + grantee and all the other users the grantee has granted the same permission + to, will also get their permission revoked. +#> +function Revoke-SqlDscServerPermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Login')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ServerRole')] + [Microsoft.SqlServer.Management.Smo.ServerRole] + $ServerRole, + + [Parameter(Mandatory = $true)] + [SqlServerPermission[]] + $Permission, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithGrant, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + # Determine which principal object we're working with + if ($PSCmdlet.ParameterSetName -eq 'Login') + { + $principalName = $Login.Name + $serverObject = $Login.Parent + } + else + { + $principalName = $ServerRole.Name + $serverObject = $ServerRole.Parent + } + + $verboseDescriptionMessage = $script:localizedData.ServerPermission_Revoke_ShouldProcessVerboseDescription -f $principalName, $serverObject.InstanceName, ($Permission -join ',') + $verboseWarningMessage = $script:localizedData.ServerPermission_Revoke_ShouldProcessVerboseWarning -f $principalName + $captionMessage = $script:localizedData.ServerPermission_Revoke_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + # Convert enum array to ServerPermissionSet object + $permissionSet = [Microsoft.SqlServer.Management.Smo.ServerPermissionSet]::new() + foreach ($permissionName in $Permission) + { + $permissionSet.$permissionName = $true + } + + # Get the permissions names that are set to $true in the ServerPermissionSet. + $permissionName = $permissionSet | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $permissionSet.$_ + } + + try + { + if ($WithGrant.IsPresent) + { + $serverObject.Revoke($permissionSet, $principalName, $false, $true) + } + else + { + $serverObject.Revoke($permissionSet, $principalName) + } + } + catch + { + $errorMessage = $script:localizedData.ServerPermission_Revoke_FailedToRevokePermission -f $principalName, $serverObject.InstanceName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'RSDSP0001', # cSpell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $principalName + ) + ) + } + } + } +} diff --git a/source/Public/Test-SqlDscServerPermission.ps1 b/source/Public/Test-SqlDscServerPermission.ps1 new file mode 100644 index 0000000000..2089dcbf50 --- /dev/null +++ b/source/Public/Test-SqlDscServerPermission.ps1 @@ -0,0 +1,253 @@ +<# + .SYNOPSIS + Tests if server permissions for a principal are in the desired state. + + .DESCRIPTION + This command tests if server permissions for an existing principal on a SQL Server + Database Engine instance are in the desired state. The principal can be specified as either + a Login object (from Get-SqlDscLogin) or a ServerRole object (from Get-SqlDscRole). + + .PARAMETER Login + Specifies the Login object for which the permissions are tested. + This parameter accepts pipeline input. + + .PARAMETER ServerRole + Specifies the ServerRole object for which the permissions are tested. + This parameter accepts pipeline input. + + .PARAMETER Grant + Specifies that the test should verify if the permissions are granted to the principal. + + .PARAMETER Deny + Specifies that the test should verify if the permissions are denied to the principal. + + .PARAMETER Permission + Specifies the desired permissions. Specify multiple permissions by + providing an array of SqlServerPermission enum values that should be present in the + specified state. An empty collection can be specified to test that no permissions + are set for the principal. + + .PARAMETER WithGrant + Specifies that the principal should have the right to grant other principals + the same permission. This parameter is only valid when parameter **Grant** is + used. When this parameter is used, the effective state tested will + be 'GrantWithGrant'. + + .PARAMETER ExactMatch + Specifies that the test should verify that only the specified permissions + are present and no additional permissions exist in the specified state. + When this parameter is not used, the test will return true if the specified + permissions are present, regardless of any additional permissions. + + .OUTPUTS + [System.Boolean] + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $login = $serverInstance | Get-SqlDscLogin -Name 'MyLogin' + + $isInDesiredState = Test-SqlDscServerPermission -Login $login -Grant -Permission ConnectSql, ViewServerState + + Tests if the specified permissions are granted to the login 'MyLogin'. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $role = $serverInstance | Get-SqlDscRole -Name 'MyRole' + + $isInDesiredState = $role | Test-SqlDscServerPermission -Grant -Permission AlterAnyDatabase -WithGrant + + Tests if the specified permissions are granted with grant option to the role 'MyRole'. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $login = $serverInstance | Get-SqlDscLogin -Name 'MyLogin' + + $isInDesiredState = Test-SqlDscServerPermission -Login $login -Grant -Permission @() + + Tests if the login 'MyLogin' has no permissions granted (empty permission set). + + .NOTES + The Login or ServerRole object must come from the same SQL Server instance + where the permissions will be tested. If specifying `-ErrorAction 'SilentlyContinue'` + then the command will silently continue if any errors occur. If specifying + `-ErrorAction 'Stop'` the command will throw an error on any failure. +#> +function Test-SqlDscServerPermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'LoginGrant')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'LoginDeny')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ServerRoleGrant')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ServerRoleDeny')] + [Microsoft.SqlServer.Management.Smo.ServerRole] + $ServerRole, + + [Parameter(Mandatory = $true, ParameterSetName = 'LoginGrant')] + [Parameter(Mandatory = $true, ParameterSetName = 'ServerRoleGrant')] + [System.Management.Automation.SwitchParameter] + $Grant, + + [Parameter(Mandatory = $true, ParameterSetName = 'LoginDeny')] + [Parameter(Mandatory = $true, ParameterSetName = 'ServerRoleDeny')] + [System.Management.Automation.SwitchParameter] + $Deny, + + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [SqlServerPermission[]] + $Permission, + + [Parameter(ParameterSetName = 'LoginGrant')] + [Parameter(ParameterSetName = 'ServerRoleGrant')] + [Parameter(ParameterSetName = 'LoginDeny')] + [Parameter(ParameterSetName = 'ServerRoleDeny')] + [System.Management.Automation.SwitchParameter] + $WithGrant, + + [Parameter(ParameterSetName = 'LoginGrant')] + [Parameter(ParameterSetName = 'ServerRoleGrant')] + [Parameter(ParameterSetName = 'LoginDeny')] + [Parameter(ParameterSetName = 'ServerRoleDeny')] + [System.Management.Automation.SwitchParameter] + $ExactMatch + ) + + process + { + # Determine which principal object we're working with + if ($Login) + { + $principalName = $Login.Name + $serverObject = $Login.Parent + } + else + { + $principalName = $ServerRole.Name + $serverObject = $ServerRole.Parent + } + + Write-Verbose -Message ( + $script:localizedData.ServerPermission_TestingDesiredState -f $principalName, $serverObject.InstanceName + ) + + try + { + # Determine the state based on the parameter set + if ($Grant.IsPresent) + { + $evaluateState = 'Grant' + } + elseif ($Deny.IsPresent) + { + $evaluateState = 'Deny' + } + + # Handle WithGrant parameter by adjusting the effective state + if ($WithGrant.IsPresent -and $evaluateState -eq 'Grant') + { + $evaluateState = 'GrantWithGrant' + } + + $originalErrorActionPreference = $ErrorActionPreference + + $ErrorActionPreference = 'Stop' + + # Get current permissions for the principal + $getSqlDscServerPermissionParameters = @{ + ServerObject = $serverObject + Name = $principalName + ErrorAction = 'Stop' + } + + $serverPermissionInfo = Get-SqlDscServerPermission @getSqlDscServerPermissionParameters + + $ErrorActionPreference = $originalErrorActionPreference + + if (-not $serverPermissionInfo) + { + # If no permissions exist and none are desired, that's the desired state + if ($Permission.Count -eq 0) + { + return $true + } + else + { + return $false + } + } + + # Convert current permissions to ServerPermission objects + $currentPermissions = $serverPermissionInfo | ConvertTo-SqlDscServerPermission + + # Handle empty Permission collection - check that no permissions are set + if ($Permission.Count -eq 0) + { + # Check if there are any permissions set for any state + $hasAnyPermissions = $currentPermissions | Where-Object -FilterScript { + $_.Permission.Count -gt 0 + } + + if ($hasAnyPermissions) + { + return $false + } + else + { + return $true + } + } + + # Find the current permission for the desired state + $currentPermissionForState = $currentPermissions | + Where-Object -FilterScript { + $_.State -eq $evaluateState + } + + if (-not $currentPermissionForState) + { + return $false + } + + # Check if all desired permissions are present in current state + foreach ($permissionName in $Permission) + { + if ($permissionName.ToString() -notin $currentPermissionForState.Permission) + { + return $false + } + } + + # Check that no unexpected permissions are present (only if ExactMatch is specified) + if ($ExactMatch.IsPresent) + { + $desiredPermissionNames = $Permission | ForEach-Object { $_.ToString() } + foreach ($currentPermissionName in $currentPermissionForState.Permission) + { + if ($currentPermissionName -notin $desiredPermissionNames) + { + return $false + } + } + } + + return $true + } + catch + { + # If the principal doesn't exist or there's another error, return false + Write-Verbose -Message ( + $script:localizedData.ServerPermission_Test_TestFailed -f $principalName, $_.Exception.Message + ) + + return $false + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 05392261d5..b5121d2152 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -37,6 +37,31 @@ ConvertFrom-StringData @' ServerPermission_DenyPermission = Deny the permissions '{0}' for the principal '{1}'. ServerPermission_RevokePermission = Revoke the permissions '{0}' for the principal '{1}'. + ## Grant-SqlDscServerPermission + ServerPermission_Grant_ShouldProcessVerboseDescription = Granting server permissions '{2}' for the principal '{0}' on the instance '{1}'. + ServerPermission_Grant_ShouldProcessVerboseWarning = Are you sure you want to grant server permissions for the principal '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + ServerPermission_Grant_ShouldProcessCaption = Grant server permissions + ServerPermission_Grant_FailedToGrantPermission = Failed to grant server permissions for principal '{0}' on instance '{1}'. + + ## Deny-SqlDscServerPermission + ServerPermission_Deny_ShouldProcessVerboseDescription = Denying server permissions '{2}' for the principal '{0}' on the instance '{1}'. + ServerPermission_Deny_ShouldProcessVerboseWarning = Are you sure you want to deny server permissions for the principal '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + ServerPermission_Deny_ShouldProcessCaption = Deny server permissions + ServerPermission_Deny_FailedToDenyPermission = Failed to deny server permissions for principal '{0}' on instance '{1}'. + + ## Test-SqlDscServerPermission + ServerPermission_TestingDesiredState = Testing desired state for server permissions for principal '{0}' on instance '{1}'. + ServerPermission_Test_TestFailed = Failed to test server permissions for principal '{0}': {1} + + ## Revoke-SqlDscServerPermission + ServerPermission_Revoke_ShouldProcessVerboseDescription = Revoking server permissions '{2}' for the principal '{0}' on the instance '{1}'. + ServerPermission_Revoke_ShouldProcessVerboseWarning = Are you sure you want to revoke server permissions for the principal '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + ServerPermission_Revoke_ShouldProcessCaption = Revoke server permissions + ServerPermission_Revoke_FailedToRevokePermission = Failed to revoke server permissions for principal '{0}' on instance '{1}'. + ## Class DatabasePermission InvalidTypeForCompare = Invalid type in comparison. Expected type [{0}], but the type was [{1}]. (DP0001) diff --git a/tests/Integration/Commands/Deny-SqlDscServerPermission.Integration.Tests.ps1 b/tests/Integration/Commands/Deny-SqlDscServerPermission.Integration.Tests.ps1 new file mode 100644 index 0000000000..a02e7db3a2 --- /dev/null +++ b/tests/Integration/Commands/Deny-SqlDscServerPermission.Integration.Tests.ps1 @@ -0,0 +1,138 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Deny-SqlDscServerPermission' -Tag 'IntegrationTest' { + BeforeAll { + # Check if there is a CI database instance to use for testing + $script:sqlServerInstanceName = $env:SqlServerInstanceName + + if (-not $script:sqlServerInstanceName) + { + $script:sqlServerInstanceName = 'DSCSQLTEST' + } + + # Get a computer name that will work in the CI environment + $script:computerName = Get-ComputerName + + Write-Verbose -Message ('Integration tests will run using computer name ''{0}'' and instance name ''{1}''.' -f $script:computerName, $script:sqlServerInstanceName) -Verbose + + $script:serverObject = Connect-SqlDscDatabaseEngine -ServerName $script:computerName -InstanceName $script:sqlServerInstanceName -Force + + # Use persistent test login and role created by earlier integration tests + $script:testLoginName = 'IntegrationTestSqlLogin' + $script:testRoleName = 'SqlDscIntegrationTestRole_Persistent' + + # Verify the persistent principals exist (should be created by New-SqlDscLogin and New-SqlDscRole integration tests) + $existingLogin = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'SilentlyContinue' + if (-not $existingLogin) + { + throw ('Test login {0} does not exist. Please run New-SqlDscLogin integration tests first to create persistent test principals.' -f $script:testLoginName) + } + + $existingRole = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'SilentlyContinue' + if (-not $existingRole) + { + throw ('Test role {0} does not exist. Please run New-SqlDscRole integration tests first to create persistent test principals.' -f $script:testRoleName) + } + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When denying server permissions to login' { + BeforeEach { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + Revoke-SqlDscServerPermission -Login $loginObject -Permission ViewServerState -Force -ErrorAction 'SilentlyContinue' + Revoke-SqlDscServerPermission -Login $loginObject -Permission ViewAnyDefinition -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should deny ViewServerState permission' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $null = Deny-SqlDscServerPermission -Login $loginObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + It 'Should show the permissions as denied' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + # First deny the permission + $null = Deny-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDatabase') -Force -ErrorAction 'Stop' + + # Then test if it's denied + $result = Test-SqlDscServerPermission -Login $loginObject -Deny -Permission @([SqlServerPermission]::ViewAnyDatabase) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should accept Login from pipeline' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $null = $loginObject | Deny-SqlDscServerPermission -Permission @('ViewAnyDefinition') -Force -ErrorAction 'Stop' + + # Verify the permission was denied + $result = Test-SqlDscServerPermission -Login $loginObject -Deny -Permission @([SqlServerPermission]::ViewAnyDefinition) -ErrorAction 'Stop' + $result | Should -BeTrue + } + } + + Context 'When denying server permissions to role' { + BeforeEach { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + Revoke-SqlDscServerPermission -ServerRole $roleObject -Permission 'ViewServerState' -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should deny ViewServerState permission to role' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $null = Deny-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + + # Verify the permission was denied + $result = Test-SqlDscServerPermission -ServerRole $roleObject -Deny -Permission @([SqlServerPermission]::ViewServerState) -ErrorAction 'Stop' + $result | Should -BeTrue + } + + It 'Should deny persistent AlterTrace permission to login for other tests' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $null = Deny-SqlDscServerPermission -Login $loginObject -Permission @('AlterTrace') -Force -ErrorAction 'Stop' + + # Verify the permission was denied - this denial will remain persistent for other integration tests + $result = Test-SqlDscServerPermission -Login $loginObject -Deny -Permission @([SqlServerPermission]::AlterTrace) -ErrorAction 'Stop' + $result | Should -BeTrue + } + } +} diff --git a/tests/Integration/Commands/Grant-SqlDscServerPermission.Integration.Tests.ps1 b/tests/Integration/Commands/Grant-SqlDscServerPermission.Integration.Tests.ps1 new file mode 100644 index 0000000000..af2608c66d --- /dev/null +++ b/tests/Integration/Commands/Grant-SqlDscServerPermission.Integration.Tests.ps1 @@ -0,0 +1,158 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Grant-SqlDscServerPermission Integration Tests' -Tag 'Integration' { + BeforeAll { + # Check if there is a CI database instance to use for testing + $script:sqlServerInstanceName = $env:SqlServerInstanceName + + if (-not $script:sqlServerInstanceName) + { + $script:sqlServerInstanceName = 'DSCSQLTEST' + } + + # Get a computer name that will work in the CI environment + $script:computerName = Get-ComputerName + + Write-Verbose -Message ('Integration tests will run using computer name ''{0}'' and instance name ''{1}''.' -f $script:computerName, $script:sqlServerInstanceName) -Verbose + + $script:serverObject = Connect-SqlDscDatabaseEngine -ServerName $script:computerName -InstanceName $script:sqlServerInstanceName -Force + + # Use existing persistent principals created by earlier integration tests + $script:testLoginName = 'IntegrationTestSqlLogin' + $script:testRoleName = 'SqlDscIntegrationTestRole_Persistent' + + # Verify the persistent principals exist (should be created by New-SqlDscLogin and New-SqlDscRole integration tests) + $existingLogin = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'SilentlyContinue' + if (-not $existingLogin) + { + throw ('Test login {0} does not exist. Please run New-SqlDscLogin integration tests first to create persistent test principals.' -f $script:testLoginName) + } + + $existingRole = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'SilentlyContinue' + if (-not $existingRole) + { + throw ('Test role {0} does not exist. Please run New-SqlDscRole integration tests first to create persistent test principals.' -f $script:testRoleName) + } + } + + AfterAll { + # Keep the persistent principals for other tests to use + # Do not remove $script:testLoginName and $script:testRoleName as they are managed by New-SqlDscLogin and New-SqlDscRole tests + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When granting server permissions to login' { + BeforeEach { + # Get the login object for testing + $script:loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + Revoke-SqlDscServerPermission -Login $script:loginObject -Permission 'ViewServerState' -Force -ErrorAction 'SilentlyContinue' + Revoke-SqlDscServerPermission -Login $script:loginObject -Permission 'ViewAnyDatabase' -Force -ErrorAction 'SilentlyContinue' + Revoke-SqlDscServerPermission -Login $script:loginObject -Permission 'ViewAnyDefinition' -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should grant ViewServerState permission successfully' { + $null = Grant-SqlDscServerPermission -Login $script:loginObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + + # Verify the permission was granted + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantedPermissions.PermissionType.ViewServerState | Should -BeTrue + } + + It 'Should grant multiple permissions successfully' { + $null = Grant-SqlDscServerPermission -Login $script:loginObject -Permission @('ViewServerState', 'ViewAnyDatabase') -Force -ErrorAction 'Stop' + + # Verify the permissions were granted + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantedPermissions.PermissionType.ViewServerState | Should -BeTrue + $grantedPermissions.PermissionType.ViewAnyDatabase | Should -BeTrue + } + + It 'Should grant permissions with WithGrant option' { + $null = Grant-SqlDscServerPermission -Login $script:loginObject -Permission @('ViewServerState') -WithGrant -Force -ErrorAction 'Stop' + + # Verify the permission was granted with grant option + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantWithGrantPermission = $grantedPermissions | Where-Object { $_.PermissionState -eq 'GrantWithGrant' } + $grantWithGrantPermission.PermissionType.ViewServerState | Should -BeTrue + } + + It 'Should accept Login from pipeline' { + $null = $script:loginObject | Grant-SqlDscServerPermission -Permission @('ViewAnyDefinition') -Force -ErrorAction 'Stop' + + # Verify the permission was granted + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantedPermissions.PermissionType.ViewAnyDefinition | Should -BeTrue + } + } + + Context 'When granting server permissions to role' { + BeforeEach { + # Get the role object for testing + $script:roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + Revoke-SqlDscServerPermission -ServerRole $roleObject -Permission 'ViewServerState' -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should grant ViewServerState permission to role successfully' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $null = Grant-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + + # Verify the permission was granted + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantedPermissions.PermissionType.ViewServerState | Should -BeTrue + } + + It 'Should grant persistent CreateEndpoint permission to role for other tests' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $null = Grant-SqlDscServerPermission -ServerRole $roleObject -Permission @('CreateEndpoint') -Force -ErrorAction 'Stop' + + # Verify the permission was granted - this permission will remain persistent for other integration tests + $grantedPermissions = Get-SqlDscServerPermission -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + $grantedPermissions | Should -Not -BeNullOrEmpty + $grantedPermissions.PermissionType.CreateEndpoint | Should -BeTrue + } + } +} diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index fd0cf8ca8d..332426df4b 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -37,10 +37,48 @@ integration tests. **Below are the integration tests listed in the run order, and with the dependency to each other. Dependencies are made to speed up the testing.** -Command | Run order # | Depends on # | Use instance ---- | --- | --- | --- -Install-SqlDscServer | 1 | - | - -New-SqlDscLogin | 2 | 1 (Install-SqlDscServer), Prerequisites | DSCSQLTEST + +Command | Run order # | Depends on # | Use instance | Creates persistent objects +--- | --- | --- | --- | --- +Prerequisites | 0 | - | - | Sets up dependencies +Install-SqlDscServer | 1 | 0 (Prerequisites) | - | DSCSQLTEST instance +Connect-SqlDscDatabaseEngine | 1 | 0 (Prerequisites) | DSCSQLTEST | - +Assert-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +New-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | IntegrationTestSqlLogin, SqlIntegrationTestGroup login +Get-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Disable-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Enable-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscIsLoginEnabled | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +New-SqlDscRole | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | SqlDscIntegrationTestRole_Persistent role +Get-SqlDscRole | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Grant-SqlDscServerPermission | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Grants ConnectSql permissions to existing persistent principals, CreateEndpoint to role +Get-SqlDscServerPermission | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscServerPermission | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Deny-SqlDscServerPermission | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Denies AlterTrace permission to login (persistent) +Revoke-SqlDscServerPermission | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Get-SqlDscDatabase | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +New-SqlDscDatabase | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test databases +Set-SqlDscDatabase | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscDatabase | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Get-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +New-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test alerts +Set-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Remove-SqlDscAgentAlert | 8 | 2 (New-SqlDscAgentAlert) | DSCSQLTEST | - +Remove-SqlDscDatabase | 8 | 2 (New-SqlDscDatabase) | DSCSQLTEST | - +Remove-SqlDscRole | 8 | 2 (New-SqlDscRole) | DSCSQLTEST | - +Remove-SqlDscLogin | 8 | 2 (New-SqlDscLogin) | DSCSQLTEST | - +Uninstall-SqlDscServer | 9 | 8 (Remove commands) | - | - +Install-SqlDscReportingService | 1 | 0 (Prerequisites) | - | SSRS instance +Get-SqlDscInstalledInstance | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Get-SqlDscRSSetupConfiguration | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Test-SqlDscRSInstalled | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Repair-SqlDscReportingService | 8 | 1 (Install-SqlDscReportingService) | SSRS | - +Uninstall-SqlDscReportingService | 9 | 8 (Repair-SqlDscReportingService) | - | - +Install-SqlDscBIReportServer | 1 | 0 (Prerequisites) | - | PBIRS instance +Repair-SqlDscBIReportServer | 8 | 1 (Install-SqlDscBIReportServer) | PBIRS | - +Uninstall-SqlDscBIReportServer | 9 | 8 (Repair-SqlDscBIReportServer) | - | - + ## Integration Tests @@ -55,18 +93,25 @@ Installs all the [instances](#instances). ### `New-SqlDscLogin` -Creates test logins on the DSCSQLTEST instance for use by other -integration tests. Tests SQL Server logins, Windows user logins, -and Windows group logins (using the local SqlIntegrationTestGroup). -The main test login `IntegrationTestSqlLogin` and the Windows group -login for `.\SqlIntegrationTestGroup` are left in place after the -test completes so other tests can use them for validation. +Creates the test login `IntegrationTestSqlLogin` and the Windows group +login `.\SqlIntegrationTestGroup` that remains on the instance for other +tests to use. ### `New-SqlDscRole` -Creates test server roles on the DSCSQLTEST instance for use by other -integration tests. Creates a persistent role `SqlDscIntegrationTestRole_Persistent` +Creates a persistent role `SqlDscIntegrationTestRole_Persistent` with sa owner that remains on the instance for other tests to use. + +### `Grant-SqlDscServerPermission` + +Grants `ConnectSql` permission to persistent login `IntegrationTestSqlLogin` +and `CreateEndpoint` permission to the role `SqlDscIntegrationTestRole_Persistent` + +### `Deny-SqlDscServerPermission` + +Creates a persistent `AlterTrace` denial on the persistent principals `IntegrationTestSqlLogin` +that remains for other tests to validate against. + ## Dependencies ### SqlServer module @@ -136,14 +181,14 @@ Group | Description Login | Password | Permission | Description --- | --- | --- | --- sa | P@ssw0rd1 | sysadmin | Administrator of all the Database Engine instances. -IntegrationTestSqlLogin | P@ssw0rd123! | - | SQL Server login created by New-SqlDscLogin integration tests for testing purposes. +IntegrationTestSqlLogin | P@ssw0rd123! | ConnectSql (Grant), AlterTrace (Deny) | SQL Server login created by New-SqlDscLogin integration tests. ConnectSql permission granted by Grant-SqlDscServerPermission, AlterTrace permission denied by Deny-SqlDscServerPermission integration tests for server permission testing. .\SqlIntegrationTestGroup | - | - | Windows group login created by New-SqlDscLogin integration tests for testing purposes. ### SQL Server Roles Role | Owner | Permission | Description --- | --- | --- | --- -SqlDscIntegrationTestRole_Persistent | sa | - | Server role created by New-SqlDscRole integration tests that remains on the instance for testing purposes. +SqlDscIntegrationTestRole_Persistent | sa | ConnectSql, CreateEndpoint | Server role created by New-SqlDscRole integration tests. ConnectSql and CreateEndpoint permissions granted by Grant-SqlDscServerPermission integration tests for server permission testing. ### Image media (ISO) diff --git a/tests/Integration/Commands/Revoke-SqlDscServerPermission.Integration.Tests.ps1 b/tests/Integration/Commands/Revoke-SqlDscServerPermission.Integration.Tests.ps1 new file mode 100644 index 0000000000..bd87b802f5 --- /dev/null +++ b/tests/Integration/Commands/Revoke-SqlDscServerPermission.Integration.Tests.ps1 @@ -0,0 +1,136 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Revoke-SqlDscServerPermission' -Tag 'IntegrationTest' { + BeforeAll { + # Check if there is a CI database instance to use for testing + $script:sqlServerInstanceName = $env:SqlServerInstanceName + + if (-not $script:sqlServerInstanceName) + { + $script:sqlServerInstanceName = 'DSCSQLTEST' + } + + # Get a computer name that will work in the CI environment + $script:computerName = Get-ComputerName + + Write-Verbose -Message ('Integration tests will run using computer name ''{0}'' and instance name ''{1}''.' -f $script:computerName, $script:sqlServerInstanceName) -Verbose + + $script:serverObject = Connect-SqlDscDatabaseEngine -ServerName $script:computerName -InstanceName $script:sqlServerInstanceName -Force + + # Use persistent test login and role created by earlier integration tests + $script:testLoginName = 'IntegrationTestSqlLogin' + $script:testRoleName = 'SqlDscIntegrationTestRole_Persistent' + + # Verify the persistent principals exist (should be created by New-SqlDscLogin and New-SqlDscRole integration tests) + $existingLogin = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'SilentlyContinue' + if (-not $existingLogin) + { + throw ('Test login {0} does not exist. Please run New-SqlDscLogin integration tests first to create persistent test principals.' -f $script:testLoginName) + } + + $existingRole = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'SilentlyContinue' + if (-not $existingRole) + { + throw ('Test role {0} does not exist. Please run New-SqlDscRole integration tests first to create persistent test principals.' -f $script:testRoleName) + } + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When revoking server permissions from login' { + BeforeEach { + # Grant a known permission for testing + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $null = Grant-SqlDscServerPermission -Login $loginObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + It 'Should revoke permissions' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $null = Revoke-SqlDscServerPermission -Login $loginObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + It 'Should show the permissions as no longer granted' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + # First grant the permission + $null = Grant-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDatabase') -Force -ErrorAction 'Stop' + + # Then revoke it + $null = Revoke-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDatabase') -Force -ErrorAction 'Stop' + + # Test that it's no longer granted + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ViewAnyDatabase) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + + It 'Should accept Login from pipeline' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + # First grant a permission to revoke + $null = Grant-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDefinition') -Force -ErrorAction 'Stop' + + $null = $loginObject | Revoke-SqlDscServerPermission -Permission @('ViewAnyDefinition') -Force -ErrorAction 'Stop' + + # Verify the permission was revoked + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ViewAnyDefinition) -ErrorAction 'Stop' + $result | Should -BeFalse + } + } + + Context 'When revoking server permissions from role' { + BeforeEach { + # Grant a known permission for testing + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + $null = Grant-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + It 'Should revoke permissions from role' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $null = Revoke-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + + # Test that it's no longer granted + $result = Test-SqlDscServerPermission -ServerRole $roleObject -Grant -Permission @([SqlServerPermission]::ViewServerState) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } +} diff --git a/tests/Integration/Commands/Test-SqlDscServerPermission.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscServerPermission.Integration.Tests.ps1 new file mode 100644 index 0000000000..a43ce23446 --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscServerPermission.Integration.Tests.ps1 @@ -0,0 +1,262 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscServerPermission Integration Tests' -Tag 'Integration' { + BeforeAll { + # Check if there is a CI database instance to use for testing + $script:sqlServerInstanceName = $env:SqlServerInstanceName + + if (-not $script:sqlServerInstanceName) + { + $script:sqlServerInstanceName = 'DSCSQLTEST' + } + + # Get a computer name that will work in the CI environment + $script:computerName = Get-ComputerName + + Write-Verbose -Message ('Integration tests will run using computer name ''{0}'' and instance name ''{1}''.' -f $script:computerName, $script:sqlServerInstanceName) -Verbose + + $script:serverObject = Connect-SqlDscDatabaseEngine -ServerName $script:computerName -InstanceName $script:sqlServerInstanceName -Force + + # Use persistent test login and role created by earlier integration tests + $script:testLoginName = 'IntegrationTestSqlLogin' + $script:testRoleName = 'SqlDscIntegrationTestRole_Persistent' + + # Verify the persistent principals exist (should be created by New-SqlDscLogin and New-SqlDscRole integration tests) + $existingLogin = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'SilentlyContinue' + if (-not $existingLogin) + { + throw ('Test login {0} does not exist. Please run New-SqlDscLogin integration tests first to create persistent test principals.' -f $script:testLoginName) + } + + $existingRole = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'SilentlyContinue' + if (-not $existingRole) + { + throw ('Test role {0} does not exist. Please run New-SqlDscRole integration tests first to create persistent test principals.' -f $script:testRoleName) + } + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When testing server permissions for login' { + BeforeAll { + # Set up known permissions for testing + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $null = Grant-SqlDscServerPermission -Login $loginObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + AfterAll { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $null = Revoke-SqlDscServerPermission -Login $loginObject -Permission @('ViewServerState') -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should return true when permissions match desired state' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ConnectSql, [SqlServerPermission]::ViewServerState) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when permissions do not match desired state' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::AlterAnyDatabase) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + + It 'Should accept Login object from pipeline' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = $loginObject | Test-SqlDscServerPermission -Grant -Permission @([SqlServerPermission]::ConnectSql, [SqlServerPermission]::ViewServerState) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return true when only testing specific grant permission that exists' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ConnectSql) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when testing for permission that does not exist' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::AlterAnyCredential) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + + It 'Should return true when testing for empty permission collection on principal with no additional permissions' { + # Create a temporary login for this test to ensure it has no additional permissions + $tempLoginName = 'TempTestLogin_' + (Get-Random) + $tempLoginObject = New-SqlDscLogin -ServerObject $script:serverObject -Name $tempLoginName -LoginType SqlLogin -SecureString (ConvertTo-SecureString -String 'TempPassword123!' -AsPlainText -Force) -Force -ErrorAction 'Stop' + + try { + # Test that empty permission collection returns true when no permissions are set + $result = Test-SqlDscServerPermission -Login $tempLoginObject -Grant -Permission @() -ErrorAction 'Stop' + + $result | Should -BeTrue + } + finally { + # Clean up temporary login + Remove-SqlDscLogin -Login $tempLoginObject -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return false when using ExactMatch and additional permissions exist' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + # Test with ExactMatch - should fail because ViewServerState is also granted + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ConnectSql) -ExactMatch -ErrorAction 'Stop' + + $result | Should -BeFalse + } + + It 'Should return true when using ExactMatch and permissions exactly match' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + # Test with ExactMatch - should pass because both ConnectSql and ViewServerState are granted + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ConnectSql, [SqlServerPermission]::ViewServerState) -ExactMatch -ErrorAction 'Stop' + + $result | Should -BeTrue + } + } + + Context 'When testing server permissions for role' { + BeforeAll { + # Set up known permissions for testing + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + $null = Grant-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'Stop' + } + + AfterAll { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + $null = Revoke-SqlDscServerPermission -ServerRole $roleObject -Permission @('ViewServerState') -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should return true when role permissions match desired state' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -ServerRole $roleObject -Grant -Permission @([SqlServerPermission]::ConnectSql, [SqlServerPermission]::ViewServerState) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when role permissions do not match desired state' { + $roleObject = Get-SqlDscRole -ServerObject $script:serverObject -Name $script:testRoleName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -ServerRole $roleObject -Grant -Permission @([SqlServerPermission]::CreateAnyDatabase) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } + + Context 'When testing deny permissions' { + BeforeAll { + # Set up denied permissions for testing + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + Deny-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDefinition') -Force -ErrorAction 'Stop' + } + + AfterAll { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + Revoke-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDefinition') -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should return true when testing for denied permission that exists' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Deny -Permission @([SqlServerPermission]::ViewAnyDefinition) -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when testing for denied permission that does not exist' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Deny -Permission @([SqlServerPermission]::AlterServerState) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } + + Context 'When testing grant with grant permissions' { + BeforeAll { + # Set up grant with grant permissions for testing + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $null = Grant-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDatabase') -WithGrant -Force -ErrorAction 'Stop' + } + + AfterAll { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + $null = Revoke-SqlDscServerPermission -Login $loginObject -Permission @('ViewAnyDatabase') -WithGrant -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should return true when testing for grant with grant permission that exists' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::ViewAnyDatabase) -WithGrant -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when testing for grant with grant permission that does not exist' { + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -ErrorAction 'Stop' + + $result = Test-SqlDscServerPermission -Login $loginObject -Grant -Permission @([SqlServerPermission]::CreateEndpoint) -WithGrant -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } + + Context 'When testing non-existent principal' { + It 'Should return false when testing permissions for non-existent login' { + # Create a mock login object that references a non-existent login + # We need to use a real server object but with a non-existent login name + $mockLogin = [Microsoft.SqlServer.Management.Smo.Login]::new($script:serverObject, 'NonExistentLogin') + + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission @([SqlServerPermission]::ConnectSql) -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } +} diff --git a/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 b/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 new file mode 100644 index 0000000000..785c317747 --- /dev/null +++ b/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 @@ -0,0 +1,185 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Deny-SqlDscServerPermission' -Tag 'Public' { + Context 'When testing parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'Login' + ExpectedParameters = '-Login -Permission [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ServerRole' + ExpectedParameters = '-ServerRole -Permission [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Deny-SqlDscServerPermission').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When testing parameter properties' { + It 'Should have Login as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Deny-SqlDscServerPermission').Parameters['Login'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerRole as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Deny-SqlDscServerPermission').Parameters['ServerRole'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Permission as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Deny-SqlDscServerPermission').Parameters['Permission'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Force as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Deny-SqlDscServerPermission').Parameters['Force'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When denying permissions successfully' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Deny method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Deny' -Value { + param($PermissionSet, $PrincipalName) + # Do nothing - just succeed + } -Force + } + + It 'Should deny permissions to a login' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Deny-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force + } + } + + It 'Should deny permissions to a server role' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = Deny-SqlDscServerPermission -ServerRole $mockServerRole -Permission ConnectSql -Force + } + } + } + + Context 'When denying permissions fails' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + # Mock the Deny method to throw an error + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Deny' -Value { + param($PermissionSet, $PrincipalName) + throw 'Mocked Deny failure' + } -Force + } + + It 'Should throw a descriptive error when operation fails' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + { Deny-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force } | + Should -Throw -ExpectedMessage '*Failed to deny server permissions*' + } + } + } + + Context 'When using pipeline input' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Deny method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Deny' -Value { + param($PermissionSet, $PrincipalName) + # Do nothing - just succeed + } -Force + } + + It 'Should accept Login from pipeline' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = $mockLogin | Deny-SqlDscServerPermission -Permission ConnectSql -Force + } + } + + It 'Should accept ServerRole from pipeline' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = $mockServerRole | Deny-SqlDscServerPermission -Permission ConnectSql -Force + } + } + } +} diff --git a/tests/Unit/Public/Grant-SqlDscServerPermission.Tests.ps1 b/tests/Unit/Public/Grant-SqlDscServerPermission.Tests.ps1 new file mode 100644 index 0000000000..5d7ad164fb --- /dev/null +++ b/tests/Unit/Public/Grant-SqlDscServerPermission.Tests.ps1 @@ -0,0 +1,197 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Grant-SqlDscServerPermission' -Tag 'Public' { + Context 'When testing parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'Login' + ExpectedParameters = '-Login -Permission [-WithGrant] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ServerRole' + ExpectedParameters = '-ServerRole -Permission [-WithGrant] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Grant-SqlDscServerPermission').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When testing parameter properties' { + It 'Should have Login as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Grant-SqlDscServerPermission').Parameters['Login'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerRole as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Grant-SqlDscServerPermission').Parameters['ServerRole'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Permission as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Grant-SqlDscServerPermission').Parameters['Permission'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Force as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Grant-SqlDscServerPermission').Parameters['Force'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + + It 'Should have WithGrant as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Grant-SqlDscServerPermission').Parameters['WithGrant'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When granting permissions successfully' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Grant method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Grant' -Value { + param($PermissionSet, $PrincipalName, $GrantWithGrant) + # Do nothing - just succeed + } -Force + } + + It 'Should grant permissions to a login' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Grant-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force + } + } + + It 'Should grant permissions to a server role' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = Grant-SqlDscServerPermission -ServerRole $mockServerRole -Permission ConnectSql -Force + } + } + + It 'Should handle GrantWithGrant state correctly' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Grant-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -WithGrant -Force + } + } + } + + Context 'When granting permissions fails' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + # Mock the Grant method to throw an error + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Grant' -Value { + param($PermissionSet, $PrincipalName, $GrantWithGrant) + throw 'Mocked Grant failure' + } -Force + } + + It 'Should throw a descriptive error when operation fails' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + { Grant-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force } | + Should -Throw -ExpectedMessage '*Failed to grant server permissions*' + } + } + } + + Context 'When using pipeline input' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Grant method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Grant' -Value { + param($PermissionSet, $PrincipalName, $GrantWithGrant) + # Do nothing - just succeed + } -Force + } + + It 'Should accept Login from pipeline' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = $mockLogin | Grant-SqlDscServerPermission -Permission ConnectSql -Force + } + } + + It 'Should accept ServerRole from pipeline' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = $mockServerRole | Grant-SqlDscServerPermission -Permission ConnectSql -Force + } + } + } +} diff --git a/tests/Unit/Public/Revoke-SqlDscServerPermission.Tests.ps1 b/tests/Unit/Public/Revoke-SqlDscServerPermission.Tests.ps1 new file mode 100644 index 0000000000..181ecb2001 --- /dev/null +++ b/tests/Unit/Public/Revoke-SqlDscServerPermission.Tests.ps1 @@ -0,0 +1,197 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Revoke-SqlDscServerPermission' -Tag 'Public' { + Context 'When testing parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'Login' + ExpectedParameters = '-Login -Permission [-WithGrant] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ServerRole' + ExpectedParameters = '-ServerRole -Permission [-WithGrant] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Revoke-SqlDscServerPermission').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When testing parameter properties' { + It 'Should have Login as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Revoke-SqlDscServerPermission').Parameters['Login'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerRole as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Revoke-SqlDscServerPermission').Parameters['ServerRole'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Permission as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Revoke-SqlDscServerPermission').Parameters['Permission'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Force as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Revoke-SqlDscServerPermission').Parameters['Force'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + + It 'Should have WithGrant as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Revoke-SqlDscServerPermission').Parameters['WithGrant'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When revoking permissions successfully' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Revoke method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Revoke' -Value { + param($PermissionSet, $PrincipalName, $RevokeGrant, $Cascade) + # Do nothing - just succeed + } -Force + } + + It 'Should revoke permissions from a login' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Revoke-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force + } + } + + It 'Should revoke permissions from a server role' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = Revoke-SqlDscServerPermission -ServerRole $mockServerRole -Permission ConnectSql -Force + } + } + + It 'Should handle WithGrant parameter correctly' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Revoke-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -WithGrant -Force + } + } + } + + Context 'When revoking permissions fails' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + # Mock the Revoke method to throw an error + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Revoke' -Value { + param($PermissionSet, $PrincipalName, $RevokeGrant, $Cascade) + throw 'Mocked Revoke failure' + } -Force + } + + It 'Should throw a descriptive error when operation fails' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + { Revoke-SqlDscServerPermission -Login $mockLogin -Permission ConnectSql -Force } | + Should -Throw -ExpectedMessage '*Failed to revoke server permissions*' + } + } + } + + Context 'When using pipeline input' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + # Mock the Revoke method on the server object + $mockServerObject | Add-Member -MemberType ScriptMethod -Name 'Revoke' -Value { + param($PermissionSet, $PrincipalName, $RevokeGrant, $Cascade) + # Do nothing - just succeed + } -Force + } + + It 'Should accept Login from pipeline' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = $mockLogin | Revoke-SqlDscServerPermission -Permission ConnectSql -Force + } + } + + It 'Should accept ServerRole from pipeline' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $null = $mockServerRole | Revoke-SqlDscServerPermission -Permission ConnectSql -Force + } + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscServerPermission.Tests.ps1 b/tests/Unit/Public/Test-SqlDscServerPermission.Tests.ps1 new file mode 100644 index 0000000000..2350641d9c --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscServerPermission.Tests.ps1 @@ -0,0 +1,552 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Test-SqlDscServerPermission' -Tag 'Public' { + Context 'When testing parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'LoginGrant' + ExpectedParameters = '-Login -Grant -Permission [-WithGrant] [-ExactMatch] []' + } + @{ + ExpectedParameterSetName = 'LoginDeny' + ExpectedParameters = '-Login -Deny -Permission [-WithGrant] [-ExactMatch] []' + } + @{ + ExpectedParameterSetName = 'ServerRoleGrant' + ExpectedParameters = '-ServerRole -Grant -Permission [-WithGrant] [-ExactMatch] []' + } + @{ + ExpectedParameterSetName = 'ServerRoleDeny' + ExpectedParameters = '-ServerRole -Deny -Permission [-WithGrant] [-ExactMatch] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscServerPermission').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When testing parameter properties' { + It 'Should have Login as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['Login'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerRole as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['ServerRole'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Grant as a mandatory parameter in Grant parameter sets' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['Grant'] + $grantParameterSetAttributes = $parameterInfo.Attributes | Where-Object { $_.GetType().Name -eq 'ParameterAttribute' -and ($_.ParameterSetName -eq 'LoginGrant' -or $_.ParameterSetName -eq 'ServerRoleGrant') } + $grantParameterSetAttributes.Mandatory | Should -BeTrue + } + + It 'Should have Deny as a mandatory parameter in Deny parameter sets' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['Deny'] + $denyParameterSetAttributes = $parameterInfo.Attributes | Where-Object { $_.GetType().Name -eq 'ParameterAttribute' -and ($_.ParameterSetName -eq 'LoginDeny' -or $_.ParameterSetName -eq 'ServerRoleDeny') } + $denyParameterSetAttributes.Mandatory | Should -BeTrue + } + + It 'Should have Permission as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['Permission'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Permission parameter allow empty collections' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['Permission'] + $allowEmptyCollectionAttribute = $parameterInfo.Attributes | Where-Object { $_.GetType().Name -eq 'AllowEmptyCollectionAttribute' } + $allowEmptyCollectionAttribute | Should -Not -BeNullOrEmpty + } + + It 'Should have WithGrant as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['WithGrant'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + + It 'Should have ExactMatch as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscServerPermission').Parameters['ExactMatch'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When testing permissions successfully' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock ServerPermissionInfo with ConnectSql permission in Grant state + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + + $mockPermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionSet' + $mockPermissionSet.ConnectSql = $true + + $mockInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionInfo' + $mockInfo.PermissionState = $mockState # Will be set by parameter filter + $mockInfo.PermissionType = $mockPermissionSet + + $mockPermissionInfo += $mockInfo + + return $mockPermissionInfo + } -ParameterFilter { $mockState = 'Grant'; $true } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @('ConnectSql') + } + [PSCustomObject] @{ + State = 'GrantWithGrant' + Permission = @('ConnectSql') + } + [PSCustomObject] @{ + State = 'Deny' + Permission = @('ConnectSql') + } + ) + } + } + + It 'Should return true when testing grant permissions for login' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission ConnectSql + + $result | Should -BeTrue + } + } + + It 'Should return true when testing deny permissions for login' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Deny -Permission ConnectSql + + $result | Should -BeTrue + } + } + + It 'Should return true when testing grant permissions for server role' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $result = Test-SqlDscServerPermission -ServerRole $mockServerRole -Grant -Permission ConnectSql + + $result | Should -BeTrue + } + } + + It 'Should return true when testing deny permissions for server role' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $result = Test-SqlDscServerPermission -ServerRole $mockServerRole -Deny -Permission ConnectSql + + $result | Should -BeTrue + } + } + + It 'Should handle WithGrant parameter correctly' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission ConnectSql -WithGrant + + $result | Should -BeTrue + } + } + + It 'Should call Get-SqlDscServerPermission with correct parameters' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $null = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission ConnectSql + + Should -Invoke -CommandName Get-SqlDscServerPermission -Times 1 -ParameterFilter { + $ServerObject -ne $null -and + $Name -eq 'TestUser' -and + $ErrorAction -eq 'Stop' + } + } + } + } + + Context 'When permissions are not in desired state' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock ServerPermissionInfo with ViewServerState permission in Grant state (not the desired ConnectSql) + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + + $mockPermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionSet' + $mockPermissionSet.ViewServerState = $true + + $mockInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionInfo' + $mockInfo.PermissionState = 'Grant' + $mockInfo.PermissionType = $mockPermissionSet + + $mockPermissionInfo += $mockInfo + + return $mockPermissionInfo + } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @('ViewServerState') # Different permission than what's being tested + } + ) + } + } + + It 'Should return false when permissions are not in desired state' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission ConnectSql + + $result | Should -BeFalse + } + } + } + + Context 'When testing fails with an exception' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + Mock -CommandName Get-SqlDscServerPermission -MockWith { + throw 'Mock error' + } + } + + It 'Should return false when an exception occurs' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission ConnectSql + + $result | Should -BeFalse + } + } + } + + Context 'When testing with empty permission collection' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + } + + Context 'When no permissions exist' { + BeforeAll { + Mock -CommandName Get-SqlDscServerPermission -MockWith { + return $null + } + } + + It 'Should return true when no permissions exist and empty collection is desired' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission @() + + $result | Should -BeTrue + } + } + } + + Context 'When permissions exist but empty collection is desired' { + BeforeAll { + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock ServerPermissionInfo with ConnectSql permission in Grant state + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + + $mockPermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionSet' + $mockPermissionSet.ConnectSql = $true + + $mockInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionInfo' + $mockInfo.PermissionState = 'Grant' + $mockInfo.PermissionType = $mockPermissionSet + + $mockPermissionInfo += $mockInfo + + return $mockPermissionInfo + } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @('ConnectSql') + } + ) + } + } + + It 'Should return false when permissions exist but empty collection is desired' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission @() + + $result | Should -BeFalse + } + } + } + + Context 'When no permissions exist and empty collection is desired' { + BeforeAll { + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock empty ServerPermissionInfo (no permissions) + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + return $mockPermissionInfo + } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @() + } + [PSCustomObject] @{ + State = 'Deny' + Permission = @() + } + [PSCustomObject] @{ + State = 'GrantWithGrant' + Permission = @() + } + ) + } + } + + It 'Should return true when no permissions are set and empty collection is desired' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = Test-SqlDscServerPermission -Login $mockLogin -Grant -Permission @() + + $result | Should -BeTrue + } + } + } + } + + Context 'When using pipeline input' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + $mockServerRole = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerRole' -ArgumentList $mockServerObject, 'TestRole' + + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock ServerPermissionInfo with ConnectSql permission in Grant state + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + + $mockPermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionSet' + $mockPermissionSet.ConnectSql = $true + + $mockInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionInfo' + $mockInfo.PermissionState = 'Grant' + $mockInfo.PermissionType = $mockPermissionSet + + $mockPermissionInfo += $mockInfo + + return $mockPermissionInfo + } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @('ConnectSql') + } + ) + } + } + + It 'Should accept Login from pipeline' { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + } -ScriptBlock { + $result = $mockLogin | Test-SqlDscServerPermission -Grant -Permission ConnectSql + + $result | Should -BeTrue + } + } + + It 'Should accept ServerRole from pipeline' { + InModuleScope -Parameters @{ + mockServerRole = $mockServerRole + } -ScriptBlock { + $result = $mockServerRole | Test-SqlDscServerPermission -Grant -Permission ConnectSql + + $result | Should -BeTrue + } + } + } + + Context 'When testing ExactMatch parameter functionality' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'MockInstance' + + $mockLogin = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList $mockServerObject, 'TestUser' + + Mock -CommandName Get-SqlDscServerPermission -MockWith { + # Mock ServerPermissionInfo with multiple permissions + $mockPermissionInfo = [Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[]] @() + + $mockPermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionSet' + $mockPermissionSet.AlterAnyEndpoint = $true + $mockPermissionSet.AlterAnyAvailabilityGroup = $true + + $mockInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerPermissionInfo' + $mockInfo.PermissionState = 'Grant' + $mockInfo.PermissionType = $mockPermissionSet + + $mockPermissionInfo += $mockInfo + + return $mockPermissionInfo + } + + Mock -CommandName ConvertTo-SqlDscServerPermission -MockWith { + return @( + [PSCustomObject] @{ + State = 'Grant' + Permission = @('AlterAnyEndpoint', 'AlterAnyAvailabilityGroup') + } + [PSCustomObject] @{ + State = 'GrantWithGrant' + Permission = @() + } + [PSCustomObject] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + + It 'Should return when requesting permission with ExactMatch ' -ForEach @( + @{ + RequestedPermission = @('AlterAnyEndpoint') + ExactMatch = $false + Expected = $true + Description = 'Should return true when testing for subset without ExactMatch' + } + @{ + RequestedPermission = @('AlterAnyEndpoint') + ExactMatch = $true + Expected = $false + Description = 'Should return false when testing for subset with ExactMatch' + } + @{ + RequestedPermission = @('AlterAnyEndpoint', 'AlterAnyAvailabilityGroup') + ExactMatch = $false + Expected = $true + Description = 'Should return true when testing for exact match without ExactMatch' + } + @{ + RequestedPermission = @('AlterAnyEndpoint', 'AlterAnyAvailabilityGroup') + ExactMatch = $true + Expected = $true + Description = 'Should return true when testing for exact match with ExactMatch' + } + ) { + InModuleScope -Parameters @{ + mockLogin = $mockLogin + RequestedPermission = $RequestedPermission + ExactMatch = $ExactMatch + Expected = $Expected + Description = $Description + } -ScriptBlock { + $testParameters = @{ + Login = $mockLogin + Grant = $true + Permission = $RequestedPermission + } + + if ($ExactMatch) + { + $testParameters['ExactMatch'] = $true + } + + $result = Test-SqlDscServerPermission @testParameters + + $result | Should -Be $Expected -Because $Description + } + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 003c52987f..dcc927c45f 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -539,13 +539,16 @@ public class ServerRole { public ServerRole( Server server, string name ) { this.Name = name; + this.Parent = server; } public ServerRole( Object server, string name ) { this.Name = name; + this.Parent = (Server)server; } public string Name; + public Server Parent; }