Providers/Authentik/Public/New-AuthentikBinding.ps1

function New-AuthentikBinding {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the seeded group, user and policy bindings from Data\AuthentikBindings.csv
    #>


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

        [Parameter()]
        [switch]$PassThru
    )

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

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

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

    $result = [PSCustomObject]@{
        TotalBindings    = $rows.Count
        CreatedBindings  = 0
        ExistingBindings = 0
        Bindings         = @()
        Errors           = @()
    }

    # Every target and subject the CSV can name, resolved once. Each map is keyed the way the
    # CSV writes it, so a row never sees a UUID.
    $targets = @{}
    $subjects = @{}

    foreach ($application in (Get-AuthentikSeededObject -Type Applications -Connection $connection)) {
        $key = $application.slug.Substring($marker.SlugPrefix.Length + 1)
        $targets["app:$key"] = [string]$application.pbm_uuid
    }
    foreach ($entitlement in (Get-AuthentikSeededObject -Type Entitlements -Connection $connection)) {
        if ($entitlement.attributes.PSObject.Properties['labKey']) { $targets["entitlement:$($entitlement.attributes.labKey)"] = [string]$entitlement.pbm_uuid }
    }
    foreach ($rule in (Get-AuthentikSeededObject -Type NotificationRules -Connection $connection)) {
        # A notification rule is itself a policy-binding model, so its pk is the target.
        $targets["rule:$($rule.name.Substring($marker.Prefix.Length))"] = [string]$rule.pk
    }
    foreach ($group in (Get-AuthentikSeededObject -Type Groups -Connection $connection)) {
        if ($group.attributes.PSObject.Properties['labKey']) { $subjects["group:$($group.attributes.labKey)"] = @{ group = [string]$group.pk } }
    }
    foreach ($user in (Get-AuthentikSeededObject -Type Users -Connection $connection)) {
        $subjects["user:$($user.username)"] = @{ user = [int]$user.pk }
    }
    foreach ($policy in (Get-AuthentikSeededObject -Type Policies -Connection $connection)) {
        $subjects["policy:$($policy.name.Substring($marker.Prefix.Length))"] = @{ policy = [string]$policy.pk }
    }

    $existingByTarget = @{}
    $bindings = [System.Collections.Generic.List[object]]::new()

    foreach ($row in $rows) {
        $label = '{0} -> {1}' -f $row.Subject, $row.Target

        if (-not $PSCmdlet.ShouldProcess($label, 'Create Authentik policy binding')) { continue }

        try {
            if (-not $targets.ContainsKey($row.Target)) {
                $message = "Binding '$label' names target '$($row.Target)', which does not exist. Skipped."
                $result.Errors += $message
                Write-Warning $message
                continue
            }
            if (-not $subjects.ContainsKey($row.Subject)) {
                $message = "Binding '$label' names subject '$($row.Subject)', which does not exist. Skipped."
                $result.Errors += $message
                Write-Warning $message
                continue
            }

            $targetUuid = $targets[$row.Target]
            $subject = $subjects[$row.Subject]

            if (-not $existingByTarget.ContainsKey($targetUuid)) {
                $existingByTarget[$targetUuid] = @(Invoke-AuthentikRequest -Method GET -Path '/policies/bindings/' `
                        -Query @{ target = $targetUuid } -Connection $connection -Paginate)
            }
            $duplicate = @($existingByTarget[$targetUuid] | Where-Object {
                    ($subject.ContainsKey('group') -and [string]$_.group -eq $subject.group) -or
                    ($subject.ContainsKey('user') -and [string]$_.user -eq [string]$subject.user) -or
                    ($subject.ContainsKey('policy') -and [string]$_.policy -eq $subject.policy)
                })

            $binding = $null
            if ($duplicate.Count -gt 0) {
                $binding = $duplicate[0]
                $result.ExistingBindings++
                Write-Verbose "Binding $label already exists"
            }
            else {
                $body = @{
                    target  = $targetUuid
                    order   = [int]$row.Order
                    enabled = ($row.Enabled -eq 'TRUE')
                    negate  = ($row.Negate -eq 'TRUE')
                }
                foreach ($k in $subject.Keys) { $body[$k] = $subject[$k] }
                $binding = Invoke-AuthentikRequest -Method POST -Path '/policies/bindings/' -Body $body -Connection $connection
                $result.CreatedBindings++
                Write-Verbose "Created binding $label"
            }

            $bindings.Add([PSCustomObject]@{
                    Id      = [string]$binding.pk
                    Target  = $row.Target
                    Subject = $row.Subject
                    Order   = [int]$row.Order
                    Enabled = ($row.Enabled -eq 'TRUE')
                })
        }
        catch {
            $message = "Failed to create binding '$label': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    $result.Bindings = $bindings.ToArray()

    Write-Verbose ("Bindings: $($result.CreatedBindings) created, $($result.ExistingBindings) existing, " +
        "$($result.Errors.Count) problems")

    if ($PassThru) { return $result }
}