Providers/Authentik/Public/New-AuthentikPolicy.ps1

function New-AuthentikPolicy {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the seeded Authentik policies from Data\AuthentikPolicies.csv and binds them to applications
    #>


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

        [Parameter()]
        [switch]$SkipBinding,

        [Parameter()]
        [switch]$PassThru
    )

    $connection = Get-AuthentikConnection
    $marker = Get-AuthentikSeedMarker -Connection $connection

    $csvPath = Join-Path -Path (Get-AuthentikDataPath) -ChildPath 'AuthentikPolicies.csv'
    $rows = @(Import-Csv -Path $csvPath -Encoding UTF8)

    if ($PolicyName) {
        $rows = @($rows | Where-Object { $PolicyName -contains $_.Name })
        $unknown = @($PolicyName | Where-Object { $rows.Name -notcontains $_ })
        if ($unknown) { throw "No definition in $csvPath for: $($unknown -join ', ')" }
    }

    # Each policy type has its own endpoint for create and update, and one shared endpoint for
    # listing and deleting. The model name is what the shared listing reports, so an existing
    # policy can be updated at the endpoint its type owns.
    $typePath = @{
        Expression   = 'expression'
        Password     = 'password'
        Reputation   = 'reputation'
        GeoIP        = 'geoip'
        EventMatcher = 'event_matcher'
    }
    $typeByModel = @{
        'authentik_policies_expression.expressionpolicy'     = 'Expression'
        'authentik_policies_password.passwordpolicy'         = 'Password'
        'authentik_policies_reputation.reputationpolicy'     = 'Reputation'
        'authentik_policies_geoip.geoippolicy'               = 'GeoIP'
        'authentik_policies_event_matcher.eventmatcherpolicy' = 'EventMatcher'
    }

    $result = [PSCustomObject]@{
        TotalPolicies   = $rows.Count
        CreatedPolicies = 0
        UpdatedPolicies = 0
        BindingsCreated = 0
        Policies        = @()
        Errors          = @()
    }

    $existingByName = @{}
    foreach ($existing in (Get-AuthentikSeededObject -Type Policies -Connection $connection)) {
        $existingByName[[string]$existing.name] = $existing
    }

    $targetBySlug = @{}
    if (-not $SkipBinding) {
        foreach ($application in (Get-AuthentikSeededObject -Type Applications -Connection $connection)) {
            $targetBySlug[[string]$application.slug] = [string]$application.pbm_uuid
        }
    }

    $policies = [System.Collections.Generic.List[object]]::new()

    foreach ($row in $rows) {
        $name = '{0}{1}' -f $marker.Prefix, $row.Name
        $type = if ($row.Type) { $row.Type } else { 'Expression' }

        if (-not $typePath.ContainsKey($type)) {
            $message = "Policy '$name' has type '$type', which the seed cannot create. Skipped."
            $result.Errors += $message
            Write-Warning $message
            continue
        }

        if (-not $PSCmdlet.ShouldProcess($name, "Create Authentik $type policy")) { continue }

        try {
            $body = @{ name = $name; execution_logging = $false }
            if ($type -eq 'Expression') {
                $body.expression = $row.Expression.Replace('{prefix}', $marker.Prefix).Replace('`n', "`n")
            }
            $settings = ConvertFrom-AuthentikSetting -Text $row.Settings
            foreach ($key in $settings.Keys) { $body[$key] = $settings[$key] }

            $policy = $null
            if ($existingByName.ContainsKey($name)) {
                $existing = $existingByName[$name]
                $existingType = $typeByModel[[string]$existing.meta_model_name]
                if ($existingType -and $existingType -ne $type) {
                    throw "A policy named '$name' already exists as a $existingType policy; the seed defines it as $type. Remove it first."
                }
                $policy = Invoke-AuthentikRequest -Method PATCH -Path "/policies/$($typePath[$type])/$($existing.pk)/" -Body $body -Connection $connection
                $result.UpdatedPolicies++
                Write-Verbose "Updated $type policy $name"
            }
            else {
                $policy = Invoke-AuthentikRequest -Method POST -Path "/policies/$($typePath[$type])/" -Body $body -Connection $connection
                $result.CreatedPolicies++
                Write-Verbose "Created $type policy $name"
            }

            $boundTo = $null
            $bound = $false
            if (-not $SkipBinding -and $row.Target) {
                $targetSlug = '{0}-{1}' -f $marker.SlugPrefix, $row.Target
                if (-not $targetBySlug.ContainsKey($targetSlug)) {
                    $message = "Policy '$name' targets application '$targetSlug', which does not exist. Created unbound."
                    $result.Errors += $message
                    Write-Warning $message
                }
                else {
                    $target = $targetBySlug[$targetSlug]
                    $existingBindings = @(Invoke-AuthentikRequest -Method GET -Path '/policies/bindings/' `
                            -Query @{ policy = [string]$policy.pk; target = $target } -Connection $connection -Paginate)
                    if ($existingBindings.Count -eq 0) {
                        $null = Invoke-AuthentikRequest -Method POST -Path '/policies/bindings/' -Connection $connection -Body @{
                            policy  = [string]$policy.pk
                            target  = $target
                            order   = [int]$row.Order
                            enabled = ($row.Enabled -eq 'TRUE')
                            negate  = ($row.Negate -eq 'TRUE')
                        }
                        $result.BindingsCreated++
                    }
                    $boundTo = $targetSlug
                    $bound = $true
                }
            }

            $policies.Add([PSCustomObject]@{
                    Id      = [string]$policy.pk
                    Key     = $row.Name
                    Name    = $name
                    Type    = $type
                    Target  = $boundTo
                    Bound   = $bound
                    Order   = $(if ($row.Order -match '^\d+$') { [int]$row.Order } else { $null })
                    Enabled = ($row.Enabled -eq 'TRUE')
                })
        }
        catch {
            $message = "Failed to create policy '$name': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    $result.Policies = $policies.ToArray()

    Write-Verbose ("Policies: $($result.CreatedPolicies) created, $($result.UpdatedPolicies) updated, " +
        "$($result.BindingsCreated) bindings, $($result.Errors.Count) problems")

    if ($PassThru) { return $result }
}