diff --git a/src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 b/src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 index 7013d6c2..470d579e 100644 --- a/src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 +++ b/src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 @@ -18,10 +18,6 @@ function New-IdleExchangeOnlineAdapter { [switch] $UseRestApi ) - # Regex patterns for sanitizing error messages (captured by scriptblock closure) - $bearerTokenPattern = 'Bearer\s+[^\s]+' - $tokenAssignmentPattern = 'token[^\s]*\s*=\s*[^\s,;]+' - $adapter = [pscustomobject]@{ PSTypeName = 'IdLE.ExchangeOnlineAdapter' UseRestApi = [bool]$UseRestApi @@ -37,6 +33,10 @@ function New-IdleExchangeOnlineAdapter { [hashtable] $Parameters = @{} ) + # Regex patterns for sanitizing error messages (defined inside scriptblock for reliable scoping) + $bearerTokenPattern = 'Bearer\s+[^\s]+' + $tokenAssignmentPattern = 'token[^\s]*\s*=\s*[^\s,;]+' + try { $result = & $CommandName @Parameters return $result diff --git a/tests/Providers/ExchangeOnlineProvider.Tests.ps1 b/tests/Providers/ExchangeOnlineProvider.Tests.ps1 index a033492c..24447515 100644 --- a/tests/Providers/ExchangeOnlineProvider.Tests.ps1 +++ b/tests/Providers/ExchangeOnlineProvider.Tests.ps1 @@ -445,6 +445,46 @@ Describe 'ExchangeOnline provider - Unit tests' { } } + Context 'New-IdleExchangeOnlineAdapter - InvokeSafely scoping regression' { + BeforeAll { + # Import private adapter function directly for unit testing + $repoRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent + $adapterPath = Join-Path -Path $repoRoot -ChildPath 'src\IdLE.Provider.ExchangeOnline\Private\New-IdleExchangeOnlineAdapter.ps1' + + if (-not (Test-Path -LiteralPath $adapterPath)) { + throw "Private adapter function file not found at path: $adapterPath" + } + . $adapterPath + + # Dot-source provider test helpers so Invoke-IdleTestBearerTokenError is available at run time + . (Join-Path -Path $PSScriptRoot -ChildPath '_testHelpers.Providers.ps1') + } + + It 'InvokeSafely can be called from another ScriptMethod without variable-not-set error' { + # Regression test: $bearerTokenPattern and $tokenAssignmentPattern must be in scope + # when InvokeSafely is invoked via $this.InvokeSafely() from another ScriptMethod. + $adapter = New-IdleExchangeOnlineAdapter + + # Wrap the real adapter with a ScriptMethod that calls $this.InvokeSafely() to + # simulate the same execution path as GetMailbox -> InvokeSafely. + $adapter | Add-Member -MemberType ScriptMethod -Name TestViaMethod -Value { + $this.InvokeSafely('Write-Output', @{ InputObject = 'ok' }) + } -Force + + { $adapter.TestViaMethod() } | Should -Not -Throw + } + + It 'InvokeSafely sanitizes bearer tokens in error messages without variable-not-set error' { + $adapter = New-IdleExchangeOnlineAdapter + + $adapter | Add-Member -MemberType ScriptMethod -Name TestErrorSanitization -Value { + $this.InvokeSafely('Invoke-IdleTestBearerTokenError', @{}) + } -Force + + { $adapter.TestErrorSanitization() } | Should -Throw -ExpectedMessage "*Bearer *" + } + } + Context 'Normalize-IdleExchangeOnlineAutoReplyMessage' { BeforeAll { # Import the private normalization function for direct testing diff --git a/tests/Providers/_testHelpers.Providers.ps1 b/tests/Providers/_testHelpers.Providers.ps1 index 58fe7df3..c99077a9 100644 --- a/tests/Providers/_testHelpers.Providers.ps1 +++ b/tests/Providers/_testHelpers.Providers.ps1 @@ -13,5 +13,18 @@ directly by test files. #> # Provider-specific helpers will be added here as needed. -# This file is currently empty but provides a clear extension point for -# provider-related test infrastructure. + +function Invoke-IdleTestBearerTokenError { + <# + .SYNOPSIS + Test helper: throws an exception whose message contains a bearer token. + + .DESCRIPTION + Used by adapter unit tests to verify that InvokeSafely correctly sanitizes + bearer tokens from error messages without leaking sensitive data. + #> + [CmdletBinding()] + param() + + throw 'Authentication failed: Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig' +}