CallOktaAPI.ps1

#Requires -Module OktaAPI
Import-Module OktaAPI

# $token comes from Okta Admin > Security > API > Tokens > Create Token
# see https://developer.okta.com/docs/api/getting_started/getting_a_token

# Call Connect-Okta before calling Okta API functions. Replace YOUR_API_TOKEN and YOUR_ORG with your values.
# Connect-Okta "YOUR_API_TOKEN" "https://YOUR_ORG.oktapreview.com"
# Or place the Connect-Okta call in OktaAPISettings.ps1
.\OktaAPISettings.ps1

# This file contains functions with sample code. To use one, call it.

# Apps

function Add-SwaApp() {
    $user = Get-OktaUser "me"
    
    # see https://developer.okta.com/docs/api/resources/apps#add-custom-swa-application
    $app = @{
        label = "AAA Test App"
        settings = @{
            signOn = @{loginUrl = "https://aaatest.oktapreview.com"}
        }
        signOnMode = "AUTO_LOGIN"
        visibility = @{autoSubmitToolbar = $false}
    }
    $app = New-OktaApp $app

    # see https://developer.okta.com/docs/api/resources/apps#assign-user-to-application-for-sso
    $appuser = @{id = $user.id; scope = "USER"}
    Add-OktaAppUser $app.id $appuser
}

function Add-AppUser() {
    $app = Get-OktaApp "?q=A bookmark app"
    $user = Get-OktaUser "me"
    $appuser = @{id = $user.id; scope = "USER"}
    $appuser = Add-OktaAppUser $app.id $appuser
}

function Get-AllMyApps() {
    $user = Get-OktaUser "me"

    $totalApps = 0
    $params = @{filter = "user.id eq `"$($user.id)`""}
    do {
        $page = Get-OktaApps @params
        $apps = $page.objects
        foreach ($app in $apps) {
            Write-Host $app.id $app.label
        }
        $totalApps += $apps.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    "$totalApps apps found."
}

function Get-PagedAppUsers($appid) {
    $totalAppUsers = 0
    $params = @{appid = $appid}
    do {
        $page = Get-OktaAppUsers @params
        $appusers = $page.objects
        foreach ($appuser in $appusers) {
            Write-Host $appuser.credentials.userName
        }
        $totalAppUsers += $appusers.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    "$totalAppUsers app users found."
}

function Get-AppGroups($appid) {
    $total = 0
    $params = @{appid = $appid}
    do {
        $page = Get-OktaAppGroups @params
        $appgroups = $page.objects
        foreach ($appgroup in $appgroups) {
            $group = Get-OktaGroup $appgroup.id
            $licenses = $appgroup.profile.licenses -join ";"
            Write-Host $appgroup.id "," $group.profile.name "," $licenses
        }
        $total += $appgroups.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    "$total app groups found."
}

function Set-AppUsers($appid) {
    $users = Import-Csv appusers.csv
    foreach ($user in $users) {
        $appUser = @{
            credentials = @{userName = $user.userName}
            scope = "USER"
        }
        $updAppUser = Set-OktaAppUser $appid $user.id $appUser
    }
}

function Remove-AppUser($appid, $login) {
    $user = Get-OktaUser $login
    $appUser = Set-OktaAppUser $appid $user.id @{scope = "USER"}
    Remove-OktaAppUser $appid $user.id
}

# Events

function Get-Events() {
    $today = Get-Date -Format "yyyy-MM-dd"
    Get-OktaEvents "$($today)T00:00:00.0-08:00"
    # Get-OktaEvents -filter 'published gt "2015-12-21T16:00:00.0-08:00"'
}

# Factors

function Get-MfaUsers {
    $totalUsers = 0
    $mfaUsers = @()
    # for more filters, see https://developer.okta.com/docs/api/resources/users#list-users-with-a-filter
    $params = @{filter = 'status eq "ACTIVE"'}
    do {
        $page = Get-OktaUsers @params
        $users = $page.objects
        foreach ($user in $users) {
            $factors = Get-OktaFactors $user.id

            $sms = $factors.where({$_.factorType -eq "sms"})
            $call = $factors.where({$_.factorType -eq "call"})

            $mfaUsers += [PSCustomObject]@{
                id = $user.id
                name = $user.profile.login
                sms = $sms.factorType
                sms_enrolled = $sms.created
                sms_status = $sms.status
                call = $call.factorType
                call_enrolled = $call.created
                call_status = $call.status
            }
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    $mfaUsers | Export-Csv mfaUsers.csv -notype
    "$totalUsers users found."
}

function Set-SMSFactor($userid) {
    $factor = @{factorType = "sms"; provider = "OKTA"; profile = @{phoneNumber = "+1-562-555-1212"}}
    $activate = $true # Activate SMS without sending one to the user.
    Set-OktaFactor $userid $factor $activate
}

function Enable-SMSFactor($userid, $factorid, $passCode) {
    # Wait for the user to get an SMS with a passCode, then enable the factor:
    Enable-OktaFactor $userid $factorid @{passCode = $passCode}
}

function Set-RSAFactor($userid) {
    $factor = @{factorType = "token"; provider = "RSA"; profile = @{credentialId = ""}; verify = @{passCode = ""}}
    Set-OktaFactor $userid $factor
}

# Groups

# Read groups from CSV and create them in Okta.
function New-Groups() {
<# Sample groups.csv file with 2 fields. Make sure you include the header line as the first record.
name,description
PowerShell Group,Members of this group are awesome.
#>

    $groups = Import-Csv groups.csv
    $importedGroups = @()
    foreach ($group in $groups) {
        $profile = @{name = $group.name; description = $group.description}
        try {
            $oktaGroup = New-OktaGroup @{profile = $profile}
            $message = "New group"
        } catch {
            Get-Error $_
            try {
                $oktaGroup = Get-OktaGroups $group.name 'type eq "OKTA_GROUP"'
                $message = "Found group"
            } catch {
                Get-Error $_
                $oktaGroup = $null
                $message = "Invalid group"
            }
        }
        $importedGroups += [PSCustomObject]@{id = $oktaGroup.id; name = $group.name; message = $message}
    }
    $importedGroups | Export-Csv importedGroups.csv -notype
    "$($groups.count) groups read." 
}

function Get-PagedGroupMembers($groupId) {
    $totalUsers = 0
    $params = @{id = $groupId; paged = $true}
    do {
        $page = Get-OktaGroupMember @params
        $users = $page.objects
        foreach ($user in $users) {
            Write-Host $user.profile.login
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl; paged = $true}
    } while ($page.nextUrl)
    "$totalUsers users found."
}

function Add-GroupMember() {
    $user = Get-OktaUser "me"
    $group = Get-OktaGroups "PowerShell" 'type eq "OKTA_GROUP"'
    Add-OktaGroupMember $group.id $user.id
}

function Export-Groups() {
    $totalGroups = 0
    $exportedGroups = @()
    # type eq "OKTA_GROUP", "APP_GROUP" (including AD/LDAP), or "BUILT_IN"
    # see https://developer.okta.com/docs/api/resources/groups#group-type
    $filter = 'type eq "APP_GROUP"' 
    $params = @{filter = $filter; paged = $true}
    do {
        $page = Get-OktaGroups @params
        $groups = $page.objects
        foreach ($group in $groups) {
            $exportedGroups += [PSCustomObject]@{id = $group.id; description = $group.profile.description; name = $group.profile.name}
        }
        $totalGroups += $groups.count
        $params = @{url = $page.nextUrl; paged = $true}
    } while ($page.nextUrl)
    $exportedGroups | Export-Csv exportedGroups.csv -notype
    "$($groups.count) groups exported." 
}

# Logs

function Get-Logs() {
    $filePath = "Log.json"
    $flush = 50 # Flush memory every N pages
    $limitRemaining = 10

    $fromTime = Get-Date -Format s
    $fromTime = $fromTime.Substring(0,10) + "T00%3A00%3A00Z"
    $toTime = Get-Date -Format s
    $toTime = $fromTime.Substring(0,10) + "T23%3A59%3A59Z"

    $allLogs = @()
    $pages = 0

    $params = @{since = $fromTime; until = $toTime; sortOrder = "DESCENDING"}
    "[" | Out-File $filePath
    do {
        $page = Get-OktaLogs @params
        $allLogs += $page.objects # these are converted from JSON, but then we convert back to JSON. TODO: optimize.
        $pages++
        if ($pages -eq $flush) {
            Flush-File $allLogs $filePath
            "," | Out-File $filePath -Append
            $allLogs = @()
            $pages = 0
        }
        if ($page.limitRemaining -lt $limitRemaining) {
            do {
                Start-Sleep 1
                $now = [DateTimeOffset]::Now.ToUnixTimeSeconds()
            } until ($now -gt $page.limitReset)
        }
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)

    Flush-File $allLogs $filePath

    "]" | Out-File $filePath -Append
}

function Flush-File($allLogs, $filePath) {
    $s = $allLogs | ConvertTo-Json -Compress -Depth 100 # max depth is 100
    $s = $s.substring(1, $s.length - 2) # remove first "[" and last "]"
    $s | Out-File $filePath -Append
}

# Users

# Read users from CSV, create them in Okta, and add to a group. See also next function.
function Import-Users() {
<# Sample users.csv file with 5 fields. Make sure you include the header line as the first record.
login,email,firstName,lastName,groupId
testa1@okta.com,testa1@okta.com,Test,A1,00g5gtwaaeOe7smEF0h7
testa2@okta.com,testa2@okta.com,Test,A2,00g5gtwaaeOe7smEF0h7
#>

    $users = Import-Csv users.csv
    $importedUsers = @()
    foreach ($user in $users) {
        $profile = @{login = $user.login; email = $user.email; firstName = $user.firstName; lastName = $user.lastName}
        $message = ""
        try {
            $oktaUser = New-OktaUser @{profile = $profile} $false
        } catch {
            try {
                $oktaUser = Get-OktaUser $user.login
            } catch {
                $oktaUser = $null
                $message = "Invalid user."
            }
        }
        if ($oktaUser) {
            try {
                Add-OktaGroupMember $user.groupId $oktaUser.id
            } catch {
                $message = "Invalid group."
            }
        }
        $importedUsers += [PSCustomObject]@{id = $oktaUser.id; login = $user.login; message = $message}
    }
    $importedUsers | Export-Csv importedUsers.csv -NoTypeInformation
    "$($users.count) users read."
}

# Read users from CSV, create them in Okta, and add to multiple groups. See also previous function.
function Import-UsersAndGroups() {
<# Sample CSV file with 5 fields. Make sure you include the header line as the first record.
login,email,firstName,lastName,groupIds
testa1@okta.com,testa1@okta.com,Test,A1,"00g5gtwaaeOe7smEF0h7;00g5gtwaaeOe7smFF0h7"
testa2@okta.com,testa2@okta.com,Test,A2,"00g5gtwaaeOe7smEF0h7;00g5gtwaaeOe7smFF0h7"
#>

    $users = Import-Csv usersAndGroups.csv
    foreach ($user in $users) {
        $profile = @{login = $user.login; email = $user.email; firstName = $user.firstName; lastName = $user.lastName}
        $groupIds = $user.groupIds -split ";"
        $oktaUser = New-OktaUser @{profile = $profile; groupIds = $groupIds}
    }
}

# ~ 1000 users / 6 min in oktapreview.com
function New-Users($numUsers) {
    $now = Get-Date -Format "yyyyMMddHHmmss"
    for ($i = 1; $i -le $numUsers; $i++) {
        $profile = @{login="a$now$i@okta.com"; email="testuser$i@okta.com"; firstName="test"; lastName="ZExp$i"}
        try {
            $user = New-OktaUser @{profile = $profile; credentials = @{password = @{value = "Password123"}}} $false
            Write-Host $i
        } catch {
            Get-Error $_
        }
    }
}

function Get-MultipleUsers() {
    $ids = "me#jane.doe".split("#")
    foreach ($id in $ids) {
        $user = Get-OktaUser $id
    }
}

function Get-User() {
    try {
        $user = Get-OktaUser "me"
        $user
    } catch {
        Get-Error $_
    }
}

function Get-PagedUsers() {
    $totalUsers = 0
    $params = @{limit = 200}
    do {
        $page = Get-OktaUsers @params
        $users = $page.objects
        foreach ($user in $users) {
            Write-Host $user.profile.login $user.credentials.provider.type
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    "$totalUsers users found."
}

function Export-Users() {
    $totalUsers = 0
    $exportedUsers = @()
    # for more filters, see https://developer.okta.com/docs/api/resources/users#list-users-with-a-filter
    $params = @{} # @{filter = 'status eq "ACTIVE"'}
    do {
        $page = Get-OktaUsers @params
        $users = $page.objects
        foreach ($user in $users) {
            $exportedUsers += [PSCustomObject]@{id = $user.id; login = $user.profile.login}
        }
        $totalUsers += $users.count
        Write-Host "$totalUsers users"
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    $exportedUsers | Export-Csv exportedUsers.csv -notype
    Write-Host "$totalUsers users exported."
    Write-Host "Done."
}

function Export-UsersAndGroups() {
    $totalUsers = 0
    $exportedUsers = @()
    # for more filters, see https://developer.okta.com/docs/api/resources/users#list-users-with-a-filter
    $params = @{filter = 'status eq "ACTIVE"'}
    do {
        $page = Get-OktaUsers @params
        $users = $page.objects
        foreach ($user in $users) {
            $userGroups = Get-OktaUserGroups $user.id
            $groups = @()
            foreach ($userGroup in $userGroups) {
                if ($userGroup.type -eq "OKTA_GROUP") {
                    $groups += $userGroup.profile.name
                }
            }
            $exportedUsers += [PSCustomObject]@{id = $user.id; login = $user.profile.login; groups = $groups -join ";"}
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    $exportedUsers | Export-Csv exportedUsersGroups.csv -notype
    "$totalUsers users exported."
}

function Rename-Users() {
    $page = Get-OktaUsers -filter 'status eq "DEPROVISIONED"'
    $users = $page.objects
    # $oktaCredUsers = $users | where {$_.credentials.provider.type -eq "OKTA"}
    foreach ($user in $users) {
        if ($user.credentials.provider.type -eq "OKTA") {
            $url = Enable-OktaUser $user.id $false
            $updatedUser = Set-OktaUser $user.id @{profile = @{email = "test@gsroka.local"}}
            Disable-OktaUser $user.id
        }
    }
    "$($users.count) users found."
}

function Remove-DeprovisionedUsers() {
    $totalUsers = 0
    $params = @{filter = 'status eq "DEPROVISIONED"'}
    do {
        $page = Get-OktaUsers @params
        $users = $page.objects
        foreach ($user in $users) {
            Write-Host $user.profile.login $user.credentials.provider.type
            Remove-OktaUser $user.id
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl}
    } while ($page.nextUrl)
    "$totalUsers users found."
}

function Remove-UsersBasedOnGroupMembership($groupId) {

# PERMANENTLY DELETE Okta Users. This won't just remove them from a group.

    $totalUsers = 0
    $params = @{id = $groupId; paged = $true}
    do {
        $page = Get-OktaGroupMember @params
        $users = $page.objects
        foreach ($user in $users) {
            Write-Host $user.profile.login
            Remove-OktaUser $user.id # First call sets user to DEPROVISIONED.
            Remove-OktaUser $user.id # Second call deletes user permanently.
        }
        $totalUsers += $users.count
        $params = @{url = $page.nextUrl; paged = $true}
    } while ($page.nextUrl)
    "$totalUsers users deleted."
}

# Zones

function New-Zone() {
    $zone = @{
        type = "IP"
        name = "test"
        gateways = @(
            @{
                type = "RANGE"
                value = "1.2.3.4"
            },
            @{
                type = "CIDR"
                value = "5.6.7.8/24"
            }
        )
    }
    New-OktaZone $zone
}

# Rate limits

# https://developer.okta.com/docs/reference/rate-limits
function Get-RateLimits() {
    $urlLimit = 1200 # this varies by URL
    $remaining = $urlLimit
    for ($i = 1; $i -le 5000; $i++) {
        $now = [DateTimeOffset]::Now.ToUnixTimeSeconds()
        if ($remaining -gt 20) {
            try {
                $page = Get-OktaUsers -filter 'profile.login eq "gabriel.sroka@lokta.com"'
                $limit = $page.limitLimit
                $remaining = $page.limitRemaining
                $reset = $page.limitReset
                Write-Host "now: " + $now + " remaining: " + $remaining + " reset: " + $reset
            } catch {
                Write-Host "Error ! (need to redo last call)."
                $remaining = 0
            }
        } else {
            Write-Host "waiting, now: " + $now + " reset: " + $reset
            if ($now -gt $reset) {
                Write-Host "reset"
                $remaining = $urlLimit
            }
            Start-Sleep -second 1
        }
    }
}


<#PSScriptInfo
.VERSION 1.1.13
.GUID 33ca8742-b9bf-4824-9d86-605a8d627cb4
.AUTHOR Gabriel Sroka
.DESCRIPTION Call Okta API.
.PROJECTURI https://github.com/gabrielsroka/OktaAPI.psm1
#>