mirror of https://github.com/raandree/NTFSSecurity
Browse Source
Add tests for the error handling that the cmdlets with -Path share: a path that doesn't exist (16 cmdlets, and 6 audit cmdlets with the Security privilege), an item whose owner may not read its permissions (6 cmdlets), and an item whose owner may not change them, which the 6 cmdlets that write the DACL handle by taking ownership. Each error belongs to its path only, and the cmdlet goes on with the next one. The tests found one defect: Get-NTFSOrphanedAccess reported an item that it couldn't read as an AddAceError with the category WriteError. It now writes a ReadSecurityError, like Get-NTFSAccess. They also show that the take-ownership retry works without privileges when the account holds the Take Ownership right and may assign the previous owner, and that it can't help a denied read, because reading the owner needs the same right. Concepts and five cmdlet pages said that the retry needs the privileges; they now describe both, and that Windows removes the OWNER RIGHTS entries when the owner changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant <ai@example.com>ai/release-5.0.0-rc6
10 changed files with 254 additions and 12 deletions
@ -0,0 +1,232 @@ |
|||||
|
<# |
||||
|
Tests the error handling that the cmdlets with -Path share, with the module built in NTFSSecurity\bin\Release on |
||||
|
files in a sandbox folder: a path that doesn't exist, an item whose owner may not read its permissions, and an item |
||||
|
whose owner may not change its permissions, which the cmdlets that write the DACL handle by taking ownership. Each |
||||
|
error belongs to its path only, and the cmdlet continues with the next one. The tests turn the module setting |
||||
|
EnablePrivileges off, so that an elevated session meets the same denials as a basic user, and restore it. |
||||
|
#> |
||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
||||
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' |
||||
|
)] |
||||
|
param () |
||||
|
|
||||
|
BeforeDiscovery { |
||||
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force |
||||
|
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege' |
||||
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value |
||||
|
$readEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' } |
||||
|
# An audit entry on a file has no inheritance flags. |
||||
|
$auditEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = 'None'; PropagationFlags = 'None' } |
||||
|
|
||||
|
# Output: whether the cmdlet writes an object for the next path, an existing file. |
||||
|
$missingPathCases = @( |
||||
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Add-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Remove-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $true }; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true } |
||||
|
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true } |
||||
|
) |
||||
|
|
||||
|
# The audit cmdlets need the Security privilege for the next path. |
||||
|
$missingPathAuditCases = @( |
||||
|
@{ Command = 'Get-NTFSAudit'; Parameters = @{} } |
||||
|
@{ Command = 'Add-NTFSAudit'; Parameters = $auditEntry } |
||||
|
@{ Command = 'Remove-NTFSAudit'; Parameters = $auditEntry } |
||||
|
@{ Command = 'Clear-NTFSAudit'; Parameters = @{} } |
||||
|
@{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{} } |
||||
|
@{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{} } |
||||
|
) |
||||
|
|
||||
|
$deniedReadCases = @( |
||||
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; Output = $false } |
||||
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; Output = $true } |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
BeforeAll { |
||||
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force |
||||
|
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1' |
||||
|
Import-Module -Name $modulePath -Force -ErrorAction Stop |
||||
|
$sandbox = New-TestSandbox -Name 'PathErrors' |
||||
|
Push-Location -LiteralPath $sandbox |
||||
|
|
||||
|
$privateData = (Get-Module -Name NTFSSecurity).PrivateData |
||||
|
$enablePrivileges = $privateData['EnablePrivileges'] |
||||
|
$privateData['EnablePrivileges'] = $false |
||||
|
$sidType = [System.Security.Principal.SecurityIdentifier] |
||||
|
|
||||
|
function Get-TestMissingPath { |
||||
|
$path = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8)) |
||||
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $path |
||||
|
$path |
||||
|
} |
||||
|
|
||||
|
function Get-TestAcl { |
||||
|
# .NET, because Get-Acl of an elevated Windows PowerShell reads also items that deny reading their permissions. |
||||
|
# .NET Core has the method as an extension method. |
||||
|
param ([string] $Path) |
||||
|
|
||||
|
$info = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path |
||||
|
if ($PSVersionTable.PSEdition -eq 'Desktop') { |
||||
|
$info.GetAccessControl() |
||||
|
} |
||||
|
else { |
||||
|
[System.IO.FileSystemAclExtensions]::GetAccessControl($info) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
AfterAll { |
||||
|
$privateData['EnablePrivileges'] = $enablePrivileges |
||||
|
Pop-Location |
||||
|
Remove-TestSandbox -Sandbox $sandbox |
||||
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue |
||||
|
} |
||||
|
|
||||
|
Describe 'A path that does not exist' { |
||||
|
It '<Command> should write a <ErrorId> for it and continue with the next path' -ForEach $missingPathCases { |
||||
|
$missing = Get-TestMissingPath |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Next' |
||||
|
|
||||
|
$output = @(& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*" |
||||
|
$pathErrors[0].TargetObject | Should -Be $missing |
||||
|
if ($Output) { |
||||
|
$output | Should -Not -BeNullOrEmpty |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
It '<Command> should write a ReadFileError for it and continue with the next path' -ForEach $missingPathAuditCases -Skip:(-not $holdsSecurityPrivilege) { |
||||
|
$privateData['EnablePrivileges'] = $true |
||||
|
try { |
||||
|
$missing = Get-TestMissingPath |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextAudit' |
||||
|
|
||||
|
& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue | Out-Null |
||||
|
} |
||||
|
finally { |
||||
|
$privateData['EnablePrivileges'] = $false |
||||
|
} |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*' |
||||
|
$pathErrors[0].TargetObject | Should -Be $missing |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'An item whose owner may not read its permissions' { |
||||
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to read the security descriptor. Taking ownership |
||||
|
# can't help: the cmdlet must read the owner first, which needs the same right. Before 5.0.0-rc6, |
||||
|
# Get-NTFSOrphanedAccess reported this as an AddAceError. |
||||
|
It '<Command> should write a ReadSecurityError for it and continue with the next path' -ForEach $deniedReadCases { |
||||
|
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'Unreadable' |
||||
|
Block-TestReadPermission -Sandbox $sandbox -Path $blocked |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextReadable' |
||||
|
|
||||
|
$output = @(& $Command -Path $blocked, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*' |
||||
|
$pathErrors[0].TargetObject | Should -Be $blocked |
||||
|
if ($Output) { |
||||
|
$output | Should -Not -BeNullOrEmpty |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'An item whose owner may not change its permissions' { |
||||
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to change the DACL. The cmdlets take ownership, |
||||
|
# which Windows answers by removing the OWNER RIGHTS entries, write the DACL, and set the previous owner back. |
||||
|
BeforeEach { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchangeable' |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$owner = $acl.GetOwner($sidType).Value |
||||
|
} |
||||
|
|
||||
|
It 'Add-NTFSAccess should take ownership, add the entry, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1 |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-3-4' }) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Remove-NTFSAccess should take ownership, remove the entry, and set the owner back' { |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Remove-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Clear-NTFSAccess should take ownership, remove the explicit entries, and set the owner back' { |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Clear-NTFSAccess -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Disable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
} |
||||
|
|
||||
|
It 'Enable-NTFSAccessInheritance should take ownership, let the DACL inherit, and set the owner back' { |
||||
|
Disable-NTFSAccessInheritance -Path $file |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Enable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeFalse |
||||
|
} |
||||
|
|
||||
|
It 'Set-NTFSInheritance should take ownership, protect the DACL, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue