runbook/AzureAutomationRunbook.ps1

param (
    [Parameter(Mandatory=$false)]
    [object]$WebhookData
)


function Authenticate-YcAAWebhookCall {
    param (
        $WebhookData
    )

    # Retrieve headers for authentication
    $incomingAPIKey = $WebhookData.RequestHeader.'X-APIKey'
    $incomingUserAgent = $WebhookData.RequestHeader.'User-Agent'
    $incomingClientId = $WebhookData.RequestHeader.'X-ClientId'
    $incomingTimestamp = $WebhookData.RequestHeader.'X-Timestamp'

    # Validate User-Agent
    $permittedUserAgents = (Get-AutomationVariable -Name "PermittedUserAgents").Split(",") | ForEach-Object { $_.Trim() }
    if ($permittedUserAgents -notcontains $incomingUserAgent) {
        throw "401: Unauthorized. Unknown User-Agent: $incomingUserAgent"
    }

    # Validate Timestamp (within 5 minutes to prevent replay attacks)
    $requestTimestamp = [datetime]::Parse($incomingTimestamp)
    $currentTime = Get-Date
    if (($currentTime - $requestTimestamp).TotalMinutes -gt 5) {
        throw "401: Unauthorized. Request expired. Request timestamp: $requestTimestamp"
    }
    
    # Validate Client ID
    $allowedClientIds = (Get-AutomationVariable -Name "AllowedClientIds").Split(",") | ForEach-Object { $_.Trim() }
    if ($allowedClientIds -notcontains $incomingClientId) {
        throw "401: Unauthorized. Invalid Client-ID: $incomingClientId"
    }

    # Validate API Key
    $APIKeyStored = Get-AutomationVariable -Name "APIKey"
    if ($incomingAPIKey -ne $APIKeyStored) {
        throw "401: Unauthorized. Invalid API Key."
    }

    # If all validations pass
    Write-YcLogMessage ("200: Successful authentication. User-Agent: $incomingUserAgent, Client-ID: $incomingClientId, Timestamp: $requestTimestamp") -ToOutput $true
}

function Validate-YcStrNotEmpty {
    param(
        [string]$Value,
        [string]$PropertyName
    )
    if ([string]::IsNullOrWhiteSpace($Value)) {
        throw "400: Bad Request. $PropertyName cannot be empty."
    }
}

if ($WebhookData -ne $null) {

    Authenticate-YcAAWebhookCall $WebhookData
    # Extract the webhook request body (payload)
    Write-YcLogMessage ("WebhookData.RequestBody is: "+$WebhookData.RequestBody) -source "ReadBody" -ToOutput $true

    try {
        $WebhookBody = $WebhookData.RequestBody | ConvertFrom-Json
        [string]$Firstname = $WebhookBody.Firstname
        [string]$Lastname = $WebhookBody.Lastname
        [string]$EmployeeId = $WebhookBody.EmployeeId
        [string]$UserName = $WebhookBody.UserName
        [string]$JobTitle = $WebhookBody.JobTitle
        [string]$CountryCode = $WebhookBody.CountryCode
        [string]$OnboardingDate = $WebhookBody.OnboardingDate
        [string]$StreetAddress = $WebhookBody.StreetAddress
        [string]$City = $WebhookBody.City
        [string]$ZipCode = $WebhookBody.ZipCode
        [string]$OfficePhone = $WebhookBody.OfficePhone
        [string]$TelephoneNumber = $WebhookBody.Telephone
        [string]$Company = $WebhookBody.Company
        [string]$Team = $WebhookBody.Team
        [string]$Department = $WebhookBody.Department
        [string]$Manager = $WebhookBody.Manager
        
    }
    catch {
        throw ("Could not extract RequestBody from WebhookData. Error details: " + $_.Exception.Message)
    }

    #Validate input values
    Validate-YcStrNotEmpty -Value $Firstname -PropertyName "Firstname"
    Validate-YcStrNotEmpty -Value $Lastname -PropertyName "Lastname"
    Validate-YcStrNotEmpty -Value $EmployeeId -PropertyName "EmployeeId"
    Validate-YcStrNotEmpty -Value $JobTitle -PropertyName "JobTitle"
    Validate-YcStrNotEmpty -Value $CountryCode -PropertyName "CountryCode"
    Validate-YcStrNotEmpty -Value $OnboardingDate -PropertyName "OnboardingDate"
    Validate-YcStrNotEmpty -Value $StreetAddress -PropertyName "StreetAddress"
    Validate-YcStrNotEmpty -Value $City -PropertyName "City"
    Validate-YcStrNotEmpty -Value $ZipCode -PropertyName "ZipCode"
    Validate-YcStrNotEmpty -Value $OfficePhone -PropertyName "OfficePhone"
    Validate-YcStrNotEmpty -Value $TelephoneNumber -PropertyName "TelephoneNumber"
    Validate-YcStrNotEmpty -Value $Company -PropertyName "Company"
    Validate-YcStrNotEmpty -Value $Team -PropertyName "Team"
    Validate-YcStrNotEmpty -Value $Department -PropertyName "Department"
    Validate-YcStrNotEmpty -Value $Manager -PropertyName "Manager"

    # Validate Username follows firstname.lastname pattern
    $expectedUserName = "$Firstname.$Lastname".ToLower()
    if ($UserName.ToLower() -ne $expectedUserName) {
        throw "400: Bad Request. Username must follow the format firstname.lastname. Expected: $expectedUserName Received: $UserName"
    }

    #Validate onboarding date is not in the past
    $onboardingDateParsed = [datetime]::Parse($OnboardingDate)
    $currentDate = Get-Date
    if ($onboardingDateParsed -lt $currentDate.Date) {
        throw "400: Bad Request. OnboardingDate cannot be in the past. Provided date: $OnboardingDate"
    }

    # If all validations pass, continue processing
    Write-YcLogMessage ("200: Validation of all input parameters was successful. Proceeding.") -ToOutput $true

} 

else {
    throw "WebhookData is null. No payload was received."
}

#Map Azure Automation Variables
try {
    $PathToMappingFile = Get-AutomationVariable -Name "PathToMappingFile"
    $PathToCsv = Get-AutomationVariable -Name "PathToCsv" 
    $OutputCSVPath =  Get-AutomationVariable -Name "OutputCSVPath"

    $APIProv_APIAppServicePrincipalId = Get-AutomationVariable -Name "APIProv_APIAppServicePrincipalId" 
    $APIProv_AzureAppRegistrationClientId = Get-AutomationVariable -Name "APIProv_AzureAppRegistrationClientId" 
    $APIProv_CertificateThumbprint = Get-AutomationVariable -Name "APIProv_CertificateThumbprint" 
    $tenantId = Get-AutomationVariable -Name "tenantId" 
}
catch {
    throw ("Could not read Azure Automation Variables. Error details: " + $_.Exception.Message)
}

#Create CSV from mapping
try {
    Write-YcLogMessage ("Creating .csv from mapping in: "+$PathToMappingFile) -ToOutput $true -Source "CreateCSVforSCIM"
    New-YcIAMCsvFromMapping -AttributeMappingFilePath $PathToMappingFile -OutputCSVPath $OutputCSVPath
    Write-YcLogMessage ("Successfully created empty .csv from mapping") -Source "CreateCSVforSCIM"
}
catch {
    throw ("Could not create .csv from mapping. Error details: " + $_.Exception.Message)
}

#Add user info from request to created csv
try {
    Write-YcLogMessage ("Adding user info to .csv.. ") -ToOutput $true
    Add-YcIAMRowToImportCSV -csvPath $OutputCSVPath -Firstname $Firstname -LastName $lastname -EmployeeId $employeeId -UserName $username -JobTitle $jobtitle -CountryCode $CountryCode -OnboardingDate $OnboardingDate -StreetAddress $StreetAddress -City $City -ZipCode $ZipCode -OfficePhone $OfficePhone -TelephoneNumber $TelephoneNumber -Company $Company -Team $team -Department $department -Manager $manager
    Write-YcLogMessage ("Successfully added user info to .csv") -ToOutput $true
}
catch {
    throw ("Could not add user info to .csv. Error Details: " + $_.Exception.Message)
}

#Wrap csv into SCIM request and send that off to Entra ID provisioning API
try {
    Write-YcLogMessage ("Sending SCIM Body as POST request to EntraID provisioning API...") -ToOutput $true
    New-YcIAMSCIMRequest -UseConfig $false -PathToCsv $OutputCSVPath -PathToMappingFile $PathToMappingFile -APIProv_APIAppServicePrincipalId $APIProv_APIAppServicePrincipalId -APIProv_AzureAppRegistrationClientId $APIProv_AzureAppRegistrationClientId -APIProv_CertificateThumbprint $APIProv_CertificateThumbprint -tenantId $tenantId
    Write-YcLogMessage ("Successfully sent SCIM Body as POST request to EntraID provisioning API. Check provisioning logs from here.") -ToOutput $true
}
catch {
    throw ("Could not send off SCIM Request. Error Details: " + $_.Exception.Message)
}