Tests/NTFS-PermissionAudit.Tests.ps1
|
#Requires -Modules Pester <# .SYNOPSIS Pester tests for the NTFS-PermissionAudit module. .DESCRIPTION Validates module loading, exported functions, parameter validation, and core logic using mocked cmdlets. No Active Directory or file system access required. #> BeforeAll { # Import the module from the project root $modulePath = Split-Path -Path $PSScriptRoot -Parent Import-Module "$modulePath\NTFS-PermissionAudit.psd1" -Force } Describe 'Module: NTFS-PermissionAudit' { Context 'Module Loading' { It 'Should import the module without errors' { $module = Get-Module -Name 'NTFS-PermissionAudit' $module | Should -Not -BeNullOrEmpty } It 'Should export exactly 5 public functions' { $module = Get-Module -Name 'NTFS-PermissionAudit' $module.ExportedFunctions.Count | Should -Be 5 } It 'Should export Invoke-PermissionAudit' { Get-Command -Module 'NTFS-PermissionAudit' -Name 'Invoke-PermissionAudit' | Should -Not -BeNullOrEmpty } It 'Should export Get-DirectUserACEs' { Get-Command -Module 'NTFS-PermissionAudit' -Name 'Get-DirectUserACEs' | Should -Not -BeNullOrEmpty } It 'Should export Get-BrokenInheritance' { Get-Command -Module 'NTFS-PermissionAudit' -Name 'Get-BrokenInheritance' | Should -Not -BeNullOrEmpty } It 'Should export Get-NestedGroupReport' { Get-Command -Module 'NTFS-PermissionAudit' -Name 'Get-NestedGroupReport' | Should -Not -BeNullOrEmpty } It 'Should export Get-SharePermissionReport' { Get-Command -Module 'NTFS-PermissionAudit' -Name 'Get-SharePermissionReport' | Should -Not -BeNullOrEmpty } It 'Should NOT export New-HtmlDashboard (private function)' { $cmd = Get-Command -Module 'NTFS-PermissionAudit' -Name 'New-HtmlDashboard' -ErrorAction SilentlyContinue $cmd | Should -BeNullOrEmpty } } Context 'Manifest Validation' { It 'Should have a valid module manifest' { $manifestPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'NTFS-PermissionAudit.psd1' { Test-ModuleManifest -Path $manifestPath -ErrorAction Stop } | Should -Not -Throw } It 'Should have the correct GUID' { $manifest = Test-ModuleManifest -Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'NTFS-PermissionAudit.psd1') $manifest.GUID.ToString() | Should -Be 'a1b2c3d4-8e7f-4a5b-9c6d-2e3f4a5b6c7d' } It 'Should require PowerShell 5.1' { $manifest = Test-ModuleManifest -Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'NTFS-PermissionAudit.psd1') $manifest.PowerShellVersion | Should -Be '5.1' } It 'Should list the correct tags' { $manifest = Test-ModuleManifest -Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'NTFS-PermissionAudit.psd1') $manifest.PrivateData.PSData.Tags | Should -Contain 'NTFS' $manifest.PrivateData.PSData.Tags | Should -Contain 'SOC2' $manifest.PrivateData.PSData.Tags | Should -Contain 'HIPAA' } } Context 'Parameter Validation' { It 'Get-DirectUserACEs: Path should be mandatory' { (Get-Command Get-DirectUserACEs).Parameters['Path'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object { $_.Mandatory } | Should -Contain $true } It 'Get-BrokenInheritance: Path should be mandatory' { (Get-Command Get-BrokenInheritance).Parameters['Path'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object { $_.Mandatory } | Should -Contain $true } It 'Get-NestedGroupReport: Path should be mandatory' { (Get-Command Get-NestedGroupReport).Parameters['Path'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object { $_.Mandatory } | Should -Contain $true } It 'Invoke-PermissionAudit: Path should be mandatory' { (Get-Command Invoke-PermissionAudit).Parameters['Path'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object { $_.Mandatory } | Should -Contain $true } It 'Get-DirectUserACEs: MaxDepth should default to 3' { (Get-Command Get-DirectUserACEs).Parameters['MaxDepth'].DefaultValue | Should -Be 3 } It 'Get-BrokenInheritance: MaxDepth should default to 3' { (Get-Command Get-BrokenInheritance).Parameters['MaxDepth'].DefaultValue | Should -Be 3 } It 'Get-NestedGroupReport: MaxNestingDepth should default to 3' { (Get-Command Get-NestedGroupReport).Parameters['MaxNestingDepth'].DefaultValue | Should -Be 3 } It 'Invoke-PermissionAudit: MaxDepth should default to 3' { (Get-Command Invoke-PermissionAudit).Parameters['MaxDepth'].DefaultValue | Should -Be 3 } It 'Get-SharePermissionReport: ComputerName should default to localhost' { (Get-Command Get-SharePermissionReport).Parameters['ComputerName'].DefaultValue | Should -Contain 'localhost' } } Context 'Get-DirectUserACEs - Mock Tests' { BeforeAll { # Create a mock ACL with both user and group entries $mockUserIdentity = [PSCustomObject]@{ Value = 'CONTOSO\jsmith' } $mockGroupIdentity = [PSCustomObject]@{ Value = 'CONTOSO\FinanceTeam' } $mockOrphanedIdentity = [PSCustomObject]@{ Value = 'S-1-5-21-1234567890-987654321-1122334455-1001' } $mockAce_User = [PSCustomObject]@{ IdentityReference = $mockUserIdentity AccessControlType = [PSCustomObject]@{ ToString = { 'Allow' } } FileSystemRights = [PSCustomObject]@{ ToString = { 'Modify, Synchronize' } } IsInherited = $false } $mockAce_Group = [PSCustomObject]@{ IdentityReference = $mockGroupIdentity AccessControlType = [PSCustomObject]@{ ToString = { 'Allow' } } FileSystemRights = [PSCustomObject]@{ ToString = { 'ReadAndExecute, Synchronize' } } IsInherited = $true } $mockAce_Orphaned = [PSCustomObject]@{ IdentityReference = $mockOrphanedIdentity AccessControlType = [PSCustomObject]@{ ToString = { 'Allow' } } FileSystemRights = [PSCustomObject]@{ ToString = { 'FullControl' } } IsInherited = $false } $mockAcl = [PSCustomObject]@{ Access = @($mockAce_User, $mockAce_Group, $mockAce_Orphaned) AreAccessRulesProtected = $false } } It 'Should flag user accounts as DIRECT USER ACE' { Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl } -ModuleName 'NTFS-PermissionAudit' # Mock AD lookups: jsmith is a user, FinanceTeam is a group Mock Get-ADGroup { throw 'Not a group' } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADUser { [PSCustomObject]@{ SamAccountName = 'jsmith' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADGroup { [PSCustomObject]@{ SamAccountName = 'FinanceTeam' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'FinanceTeam' } $results = Get-DirectUserACEs -Path 'C:\TestPath' $userEntry = $results | Where-Object { $_.Identity -eq 'CONTOSO\jsmith' } $userEntry.Finding | Should -Be 'DIRECT USER ACE' $userEntry.ObjectType | Should -Be 'User' } It 'Should mark group accounts as OK' { Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl } -ModuleName 'NTFS-PermissionAudit' Mock Get-ADGroup { throw 'Not a group' } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADUser { [PSCustomObject]@{ SamAccountName = 'jsmith' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADGroup { [PSCustomObject]@{ SamAccountName = 'FinanceTeam' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'FinanceTeam' } $results = Get-DirectUserACEs -Path 'C:\TestPath' $groupEntry = $results | Where-Object { $_.Identity -eq 'CONTOSO\FinanceTeam' } $groupEntry.Finding | Should -Be 'OK' $groupEntry.ObjectType | Should -Be 'Group' } It 'Should detect orphaned SIDs as user accounts' { Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl } -ModuleName 'NTFS-PermissionAudit' Mock Get-ADGroup { throw 'Not found' } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADUser { [PSCustomObject]@{ SamAccountName = 'jsmith' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'jsmith' } Mock Get-ADGroup { [PSCustomObject]@{ SamAccountName = 'FinanceTeam' } } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'FinanceTeam' } Mock Get-ADGroup { throw 'Not found' } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'S-1-5-21-1234567890-987654321-1122334455-1001' } Mock Get-ADUser { throw 'Not found' } -ModuleName 'NTFS-PermissionAudit' -ParameterFilter { $Identity -eq 'S-1-5-21-1234567890-987654321-1122334455-1001' } $results = Get-DirectUserACEs -Path 'C:\TestPath' $orphanedEntry = $results | Where-Object { $_.Identity -like 'S-1-5-21-*' } $orphanedEntry.ObjectType | Should -Be 'User' $orphanedEntry.Finding | Should -Be 'DIRECT USER ACE' } } Context 'Get-BrokenInheritance - Mock Tests' { It 'Should detect folders with disabled inheritance' { $mockAcl_Broken = [PSCustomObject]@{ Access = @( [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'CONTOSO\FinanceTeam' } } [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'BUILTIN\Administrators' } } ) AreAccessRulesProtected = $true } Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl_Broken } -ModuleName 'NTFS-PermissionAudit' $results = Get-BrokenInheritance -Path 'C:\TestPath' $results[0].InheritanceEnabled | Should -Be $false $results[0].Finding | Should -BeLike 'INHERITANCE DISABLED*' $results[0].ACECount | Should -Be 2 } It 'Should report OK for folders with enabled inheritance' { $mockAcl_OK = [PSCustomObject]@{ Access = @( [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'CONTOSO\Domain Users' } } ) AreAccessRulesProtected = $false } Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl_OK } -ModuleName 'NTFS-PermissionAudit' $results = Get-BrokenInheritance -Path 'C:\TestPath' $results[0].InheritanceEnabled | Should -Be $true $results[0].Finding | Should -Be 'OK' } It 'Should return correct unique identity count' { $mockAcl_Multi = [PSCustomObject]@{ Access = @( [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'CONTOSO\GroupA' } } [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'CONTOSO\GroupB' } } [PSCustomObject]@{ IdentityReference = [PSCustomObject]@{ Value = 'CONTOSO\GroupA' } } ) AreAccessRulesProtected = $true } Mock Test-Path { $true } -ModuleName 'NTFS-PermissionAudit' Mock Get-ChildItem { @() } -ModuleName 'NTFS-PermissionAudit' Mock Get-Acl { $mockAcl_Multi } -ModuleName 'NTFS-PermissionAudit' $results = Get-BrokenInheritance -Path 'C:\TestPath' $results[0].UniqueIdentities | Should -Be 2 $results[0].ACECount | Should -Be 3 } } Context 'Get-SharePermissionReport - Mock Tests' { BeforeAll { $mockShares = @( [PSCustomObject]@{ Name = 'Finance'; Path = 'D:\Shares\Finance' } [PSCustomObject]@{ Name = 'Public'; Path = 'D:\Shares\Public' } [PSCustomObject]@{ Name = 'ADMIN$'; Path = 'C:\Windows' } ) $mockAccess_Finance = @( [PSCustomObject]@{ AccountName = 'CONTOSO\FinanceTeam' AccessRight = 'Change' AccessControlType = 'Allow' } ) $mockAccess_Public = @( [PSCustomObject]@{ AccountName = 'Everyone' AccessRight = 'Full' AccessControlType = 'Allow' } ) $mockAccess_Admin = @( [PSCustomObject]@{ AccountName = 'BUILTIN\Administrators' AccessRight = 'Full' AccessControlType = 'Allow' } ) } It 'Should detect Everyone with Full Control' { Mock Get-SmbShare { $mockShares } -ModuleName 'NTFS-PermissionAudit' Mock Get-SmbShareAccess { param($Name) switch ($Name) { 'Finance' { $mockAccess_Finance } 'Public' { $mockAccess_Public } 'ADMIN$' { $mockAccess_Admin } } } -ModuleName 'NTFS-PermissionAudit' $results = Get-SharePermissionReport -ComputerName 'localhost' $everyoneEntry = $results | Where-Object { $_.AccountName -eq 'Everyone' -and $_.ShareName -eq 'Public' } $everyoneEntry.Finding | Should -BeLike '*EVERYONE FULL CONTROL*' } It 'Should exclude default admin shares when -ExcludeDefault is specified' { Mock Get-SmbShare { $mockShares } -ModuleName 'NTFS-PermissionAudit' Mock Get-SmbShareAccess { param($Name) switch ($Name) { 'Finance' { $mockAccess_Finance } 'Public' { $mockAccess_Public } 'ADMIN$' { $mockAccess_Admin } } } -ModuleName 'NTFS-PermissionAudit' $results = Get-SharePermissionReport -ComputerName 'localhost' -ExcludeDefault $adminShare = $results | Where-Object { $_.ShareName -eq 'ADMIN$' } $adminShare | Should -BeNullOrEmpty } It 'Should flag shares with no explicit deny' { Mock Get-SmbShare { @([PSCustomObject]@{ Name = 'Data'; Path = 'D:\Data' }) } -ModuleName 'NTFS-PermissionAudit' Mock Get-SmbShareAccess { @( [PSCustomObject]@{ AccountName = 'CONTOSO\DataTeam' AccessRight = 'Change' AccessControlType = 'Allow' } ) } -ModuleName 'NTFS-PermissionAudit' $results = Get-SharePermissionReport -ComputerName 'localhost' $results[0].Finding | Should -BeLike '*NO EXPLICIT DENY*' } } } |