Providers/FreeIPA/Public/New-FreeIPAIdentityProvider.ps1

function New-FreeIPAIdentityProvider {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the seeded RADIUS proxies and external identity providers from Data\FreeIPAIdentityProviders.csv
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string[]]$ProviderName,

        [Parameter()]
        [switch]$PassThru
    )

    $connection = Get-FreeIPAConnection
    $marker = Get-FreeIPASeedMarker -Connection $connection
    $zone = Get-FreeIPASeedZone -Marker $marker -Connection $connection

    $csvPath = Join-Path -Path (Get-FreeIPADataPath) -ChildPath 'FreeIPAIdentityProviders.csv'
    $rows = @(Import-Csv -Path $csvPath -Encoding UTF8)
    if ($ProviderName) {
        $rows = @($rows | Where-Object { $ProviderName -contains $_.Name })
        $unknown = @($ProviderName | Where-Object { $rows.Name -notcontains $_ })
        if ($unknown) { throw "No definition in $csvPath for: $($unknown -join ', ')" }
    }

    $result = [PSCustomObject]@{
        TotalProviders = $rows.Count
        CreatedProxies = 0
        UpdatedProxies = 0
        CreatedIdps    = 0
        UpdatedIdps    = 0
        Providers      = @()
        Errors         = @()
    }

    # {prefix} and {zone} in a server or client name become the session's prefix and the
    # seed's forward zone, so a proxy points at a seeded host's real name.
    $substitute = { param($text) ([string]$text).Replace('{prefix}', $marker.NamePrefix).Replace('{zone}', $zone.Forward) }
    $secret = { [System.Net.NetworkCredential]::new('', (New-TestPassword -Length 32)).Password }

    $existingProxies = @{}
    foreach ($entry in (Get-FreeIPASeededObject -Type RadiusProxies -Connection $connection)) { $existingProxies[[string](@($entry.cn)[0])] = $entry }
    $existingIdps = @{}
    foreach ($entry in (Get-FreeIPASeededObject -Type IdentityProviders -Connection $connection)) { $existingIdps[[string](@($entry.cn)[0])] = $entry }

    $providers = [System.Collections.Generic.List[object]]::new()
    foreach ($row in $rows) {
        $name = Resolve-FreeIPASeedName -Key $row.Name -Marker $marker -Connection $connection
        $kind = if ($row.Kind -eq 'Radius') { 'RADIUS proxy' } else { 'identity provider' }
        if (-not $PSCmdlet.ShouldProcess($name, "Create FreeIPA $kind")) { continue }
        try {
            switch ($row.Kind) {
                'Radius' {
                    $options = @{
                        description          = ('{0} {1}' -f $row.Description, $marker.Marker).Trim()
                        ipatokenradiusserver = (& $substitute $row.Server)
                    }
                    if ($row.Timeout -match '^\d+$') { $options['ipatokenradiustimeout'] = [int]$row.Timeout }
                    if ($row.Retries -match '^\d+$') { $options['ipatokenradiusretries'] = [int]$row.Retries }
                    if ($row.UserMapAttribute) { $options['ipatokenusermapattribute'] = $row.UserMapAttribute }

                    if ($existingProxies.ContainsKey($name)) {
                        # The secret stays what it is: a re-run does not rotate what it never kept.
                        $null = Invoke-FreeIPARequest -Method 'radiusproxy_mod' -Arguments $name -Options $options -Connection $connection -IgnoreError 'EmptyModlist'
                        $result.UpdatedProxies++
                    }
                    else {
                        $options['ipatokenradiussecret'] = & $secret
                        $null = Invoke-FreeIPARequest -Method 'radiusproxy_add' -Arguments $name -Options $options -Connection $connection
                        $result.CreatedProxies++
                        Write-Verbose "Created RADIUS proxy $name"
                    }
                }
                'Idp' {
                    $options = @{
                        ipaidpclientid = (& $substitute $row.ClientId)
                    }
                    if ($row.Scope) { $options['ipaidpscope'] = $row.Scope }
                    if ($row.Subject) { $options['ipaidpsub'] = $row.Subject }

                    if ($existingIdps.ContainsKey($name)) {
                        $null = Invoke-FreeIPARequest -Method 'idp_mod' -Arguments $name -Options $options -Connection $connection -IgnoreError 'EmptyModlist'
                        $result.UpdatedIdps++
                    }
                    else {
                        # The provider template is only for creation: it fills the endpoints in.
                        $options['ipaidpprovider'] = $row.Provider
                        if ($row.Org) { $options['ipaidporg'] = $row.Org }
                        if ($row.BaseUrl) { $options['ipaidpbaseurl'] = (& $substitute $row.BaseUrl) }
                        $options['ipaidpclientsecret'] = & $secret
                        $null = Invoke-FreeIPARequest -Method 'idp_add' -Arguments $name -Options $options -Connection $connection
                        $result.CreatedIdps++
                        Write-Verbose "Created identity provider $name"
                    }
                }
                default { throw "Unknown kind '$($row.Kind)' on row '$($row.Name)'." }
            }
            $providers.Add([PSCustomObject]@{ Key = $row.Name; Name = $name; Kind = $row.Kind })
        }
        catch {
            $message = "Failed to create $kind '$name': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    $result.Providers = $providers.ToArray()
    Write-Verbose "Identity providers: $($result.CreatedProxies) proxies and $($result.CreatedIdps) providers created, $($result.UpdatedProxies + $result.UpdatedIdps) updated, $($result.Errors.Count) problems"
    if ($PassThru) { return $result }
}