Providers/Okta/Private/Get-OktaSeededApp.ps1
|
function Get-OktaSeededApp { <# .SYNOPSIS Finds the app integrations this module created, separating lab apps from the service app .DESCRIPTION Two markers are required, the same belt-and-braces rule the groups use: the label starts with the prefix, AND a second marker confirms the app was created here. Requiring both matters more for apps than anywhere else, because the tenant this was developed against already held apps named after real things (Google Workspace, a Postman client, an AD agent). A label-prefix match alone is the kind of rule that eventually deletes one. The second marker is not uniform, and finding that out cost a bug. Okta ACCEPTS a profile object on any app and then silently discards it for every sign-on mode except OPENID_CONNECT - the POST succeeds, the response omits it, and a follow-up PUT does not help either. Verified against a live tenant. A teardown keyed on the profile alone therefore found one app out of eight and quietly left the rest behind. So two second markers are accepted, either of which is enough: - profile.labSeedTag equal to the prefix. Reliable for OIDC apps only. - A URL pointing at the seed email domain. Every seeded app targets that domain, it survives on every app type, and it is the same fallback the users use. The URL lives in a different place per type: settings.app.url for bookmark and SWA apps, settings.oauthClient.redirect_uris for OIDC ones. The service app is excluded by default and returned separately, so teardown can remove the lab apps while keeping the credential it authenticates with. The two are told apart by the delimiter: lab apps are "PREFIX-Label", the service app is "PREFIX Something". .PARAMETER Prefix The label prefix and seed tag to match .PARAMETER EmailDomain The seed domain the app URLs point at .PARAMETER IncludeServiceApp Return the service app as well as the lab apps .OUTPUTS Array of Okta app objects .EXAMPLE Get-OktaSeededApp -Prefix 'OKTALAB' -EmailDomain 'oktalab.example.com' .NOTES Author: Jeffrey Stuhr Version: 1.1.0 Last Updated: 2026-08-07 #> [CmdletBinding()] [OutputType([object[]])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Prefix, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$EmailDomain, [Parameter()] [switch]$IncludeServiceApp ) $apps = @(Invoke-OktaRequest -Method GET -Path '/api/v1/apps' ` -Query @{ q = $Prefix; limit = 200 } -Paginate) $domain = $EmailDomain.TrimStart('@') return @($apps | Where-Object { if (-not $_.label) { return $false } $isLabApp = $_.label.StartsWith("$Prefix-", [StringComparison]::OrdinalIgnoreCase) if (-not $isLabApp) { # The service app carries neither marker: it predates them, and its OIDC profile is # not set. It is identified by its label alone, which is safe because the prefix is # followed by a space rather than being an open-ended pattern. if ($IncludeServiceApp) { return $_.label.StartsWith("$Prefix ", [StringComparison]::OrdinalIgnoreCase) } return $false } $tagged = $_.settings -and $_.profile -and $_.profile.PSObject.Properties['labSeedTag'] -and $_.profile.labSeedTag -eq (Get-OktaSeedTag -Prefix $Prefix) if ($tagged) { return $true } # Gather every URL the app exposes, whichever shape it uses. $urls = @() if ($_.settings) { if ($_.settings.PSObject.Properties['app'] -and $_.settings.app -and $_.settings.app.PSObject.Properties['url']) { $urls += $_.settings.app.url } if ($_.settings.PSObject.Properties['oauthClient'] -and $_.settings.oauthClient) { $urls += @($_.settings.oauthClient.redirect_uris) } } foreach ($url in ($urls | Where-Object { $_ })) { $urlHost = $null try { $urlHost = ([uri]$url).Host } catch { continue } if (-not $urlHost) { continue } # Suffix match on a dot boundary, or the domain exactly. A bare -like '*domain*' # would match notoktalab.example.com.evil.test, the same trap the user lookup avoids. if ($urlHost.Equals($domain, [StringComparison]::OrdinalIgnoreCase) -or $urlHost.EndsWith(".$domain", [StringComparison]::OrdinalIgnoreCase)) { return $true } } return $false }) } |