From c9c4b31d8494e448710ddcc01c3de8d8ab7a3c0e Mon Sep 17 00:00:00 2001 From: rhouthuijzen <116062840+rhouthuijzen@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:28:37 +0200 Subject: [PATCH] New powershell connector (PSv2) (#2) * release new powershell connector * Fix: minor fixes * Fix: Script: Var * Fix: Aref var * Fix: Changes after testing * Feat: fieldMapping and Readme * Update README.md * Fix: $outputContext now correct * Fix: label description was incorrect * Fix: Update Logo URL * fix: updateOnupdate to onlyUpdateOnCorrelate * Fix: NotFound varriable was incorrect * Feat: added debug toggle --- README.md | 149 ++++++++++++--------- configuration.json | 6 +- create.ps1 | 294 +++++++++++------------------------------ delete.ps1 | 286 ++++++++++++++++------------------------ fieldMapping.json | 50 +++++++ update.ps1 | 321 ++++++++++++++++++--------------------------- 6 files changed, 460 insertions(+), 646 deletions(-) create mode 100644 fieldMapping.json diff --git a/README.md b/README.md index ed00eac..f3b2344 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,126 @@ -| :information_source: Information | -|:---------------------------| -| This repository contains the connector and configuration code only. The implementer is responsible to acquire the connection details such as username, password, certificate, etc. You might even need to sign a contract or agreement with the supplier before implementing this connector. Please contact the client's application manager to coordinate the connector requirements. | - -| :warning: Warning | -|:---------------------------| -| The latest version of this connector requires **new api credentials**. To get these, please follow the [Visma documentation on how to register the App and grant access to client data](https://community.visma.com/t5/Kennisbank-Youforce-API/Visma-Developer-portal-een-account-aanmaken-applicatie/ta-p/527059). -
+ +# HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity + +> [!IMPORTANT] +> This repository contains the connector and configuration code only. The implementer is responsible to acquire the connection details such as username, password, certificate, etc. You might even need to sign a contract or agreement with the supplier before implementing this connector. Please contact the client's application manager to coordinate the connector requirements. +

- +

-## Versioning -| Version | Description | Date | -| - | - | - | -| 1.1.2 | Performance and logging upgrades | 2022/10/25 | -| 1.1.1 | Updated checking of identity value | 2021/08/06 | -| 1.1.0 | Implementation updates | 2021/04/01 | -| 1.0.0 | Initial release | 2020/11/12 | - - -## Table of Contents -- [Versioning](#versioning) -- [Table of Contents](#table-of-contents) -- [Introduction](#introduction) -- [Introduction](#introduction-1) -- [Getting started](#getting-started) - - [Connection settings](#connection-settings) - - [Prerequisites](#prerequisites) +## Table of contents + +- [HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity](#helloid-conn-prov-target-raet-beaufort-iam-api-identity) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Getting started](#getting-started) + - [Provisioning PowerShell V2 connector](#provisioning-powershell-v2-connector) + - [Correlation configuration](#correlation-configuration) + - [Field mapping](#field-mapping) + - [Connection settings](#connection-settings) + - [Prerequisites](#prerequisites) - [Remarks](#remarks) -- [Getting help](#getting-help) -- [HelloID docs](#helloid-docs) - + - [Getting help](#getting-help) + - [HelloID docs](#helloid-docs) ## Introduction -By using this connector you will have the ability to update the 'identity' field of Raet users, using the RAET IAM API. -This connector is able to write back the identity of a provisioned user (to another target like Azure AD or MS AD) to the user of Raet Beaufort. This field can be used in Beaufort for single Sign-On purposes. Please keep in mind that for now, only the AccountCreate or AccountUpdate is triggering the possible change of the identity. (Disable, Delete and Enable are not neccesarry) +_HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity_ is a _target_ connector. _Raet-Beaufort_ provides a set of REST API's that allow you to programmatically interact with its data. The HelloID connector uses the API endpoints listed in the table below. +| Endpoint | Description | +| ------------------------------------------------- | ----------- | +| /iam/v1.0/users(employeeId={employeeId}) | GET user | +| /iam/v1.0/users(employeeId={employeeId})/identity | PATCH user | + +This connector is able to write back the identity of a provisioned user (to another target like Azure AD or MS AD) to the user of Raet Beaufort. This field can be used in Beaufort for single Sign-On purposes. Also keep in mind that this endpoint will be migrated to the new IAM-API later on. More information about the Users endpoint of the Raet Users Endpoint can be found on: -- https://community.visma.com/t5/Kennisbank-Youforce-API/IAM-user-endpoint/ta-p/430073 -- https://vr-api-integration.github.io/SwaggerUI/IAM%20Users.html +- [Community visma](https://community.visma.com/t5/Kennisbank-Youforce-API/IAM-user-endpoint/ta-p/430073) +- [Swagger](https://vr-api-integration.github.io/SwaggerUI/IAM%20Users.html) -## Introduction -By using this connector you will have the ability to update the 'identity' field of Raet users, using the RAET IAM API. -This connector is able to write back the identity of a provisioned user (to another target like Azure AD or MS AD) to the user of Raet Beaufort. This field can be used in Beaufort for single Sign-On purposes. -Also keep in mind that this endpoint will be migrated to the new IAM-API later on. -More information about the Users endpoint of the Raet Users Endpoint can be found on: -- https://community.visma.com/t5/Kennisbank-Youforce-API/IAM-user-endpoint/ta-p/430073 -- https://vr-api-integration.github.io/SwaggerUI/IAM%20Users.html -- -The HelloID connector consists of the template scripts shown in the following table. +The following lifecycle actions are available: -| Action | Action(s) Performed | Comment | -| ------------------------------- | --------------------- | --------- | -| create.ps1 | Update RAET user | | -| update.ps1 | Update RAET user | | -| delete.ps1 | Update RAET user | Clear the unique fields, since the values have to be unique in RAET | +| Action | Description | +| ------------------ | ------------------------------------ | +| create.ps1 | Correlation on person | +| delete.ps1 | Empty configured field(s) on person | +| update.ps1 | Update configured field(s) on person | +| configuration.json | Default _configuration.json_ | +| fieldMapping.json | Default _fieldMapping.json_ | ## Getting started +### Provisioning PowerShell V2 connector + +#### Correlation configuration + +The correlation configuration is used to specify which properties will be used to match an existing account within _{connectorName}_ to a person in _HelloID_. + +To properly setup the correlation: + +1. Open the `Correlation` tab. + +2. Specify the following configuration: + + | Setting | Value | + | ------------------------- | --------------------------------- | + | Enable correlation | `True` | + | Person correlation field | `PersonContext.Person.ExternalId` | + | Account correlation field | `` | + +> [!TIP] +> _For more information on correlation, please refer to our correlation [documentation](https://docs.helloid.com/en/provisioning/target-systems/powershell-v2-target-systems/correlation.html) pages_. + +#### Field mapping + +The field mapping can be imported by using the [_fieldMapping.json_](./fieldMapping.json) file. + ### Connection settings The following settings are required to connect to the API. -| Setting | Description | Example value | -| ----------------- | ------------------------------------------------------------- | ------------------------------------- | -| Client Id | The Client id for this Raet environment | A1bCdefghifjkL2MnOPQrsT3u45V6wx7Y | -| Client Secret | The Client secret for this Raet environment | 7aBcdeFgHijkLmN | -| Tenant Id | The Tenant id for this Raet environment | 1234567 | +| Setting | Description | Mandatory | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| Client ID | The Client ID to connect with the IAM API (created when registering the App in in the Visma Developer portal). | Yes | +| Client Secret | The Client Secret to connect with the IAM API (created when registering the App in in the Visma Developer portal). | Yes | +| Tenant ID | The Tenant ID to specify to which Raet tenant to connect with the IAM API (available in the Visma Developer portal after the invitation code has been accepted). | Yes | +| UpdateOnUpdate | If you also want to update the user on a update account | | ### Prerequisites -- [ ] HelloID Provisioning agent (cloud or on-prem). +> [!IMPORTANT] +> The latest version of this connector requires **new api credentials**. To get these, please follow the [Visma documentation](https://community.visma.com/t5/Kennisbank-Youforce-API/Visma-Developer-portal-een-account-aanmaken-applicatie/ta-p/527059) on how to register the App and grant access to client data. - [ ] Enabling of the User endpoints. - By default, the User endpoints aren't "enabled". This has to be requested at Raet. - [ ] ClientID, ClientSecret and tenantID - - Since we are using the API we neet the ClientID, ClientSecret and tenantID to authenticate with RAET IAM-API Webservice. + - Since we are using the API we need the ClientID, ClientSecret and tenantID to authenticate with RAET IAM-API Webservice. - [ ] Dependent account data in HelloID. - Please make your provisioned system dependent on this Users Target Connector and make sure that the values needed to be written back are stored on the account data (e.g UserPrincipalName). -#### Remarks -- Only the 'identity' field can be updated, no other fields are (currently) supported. - > When the value in Raet equals the value in HelloID, the action will be skipped (no update will take place). -- Currently (08-12-2022) Changes you make with this connector through the API are not visible within the Youforce portal. If you want to check if the update is succesfull please retreive the edited user or try the SSO connection. +### Remarks +> [!TIP] +> Only the 'identity' field can be updated, no other fields are (currently) supported. +> +> When the value in Raet equals the value in HelloID, the action will be skipped (no update will take place). + +> [!NOTE] +> Currently (08-12-2022) Changes you make with this connector through the API are not visible within the Youforce portal. If you want to check if the update is succesfull please retreive the edited user or try the SSO connection. + + +> [!NOTE] +> Currently (14-02-2024) it is not possible to empty the identity in Youforce. The endpoint will give a error. By default `ExternalID@Domain.com` will be filled. ## Getting help -> _For more information on how to configure a HelloID PowerShell connector, please refer to our [documentation](https://docs.helloid.com/hc/en-us/articles/360012558020-Configure-a-custom-PowerShell-target-system) pages_ -> _If you need help, feel free to ask questions on our [forum](https://forum.helloid.com)_ +> [!TIP] +> _For more information on how to configure a HelloID PowerShell connector, please refer to our [documentation](https://docs.helloid.com/en/provisioning/target-systems/powershell-v2-target-systems.html) pages_. + +> [!TIP] +> _If you need help, feel free to ask questions on our [forum](https://forum.helloid.com)_. ## HelloID docs + The official HelloID documentation can be found at: https://docs.helloid.com/ diff --git a/configuration.json b/configuration.json index 27fe6ad..4fc178c 100644 --- a/configuration.json +++ b/configuration.json @@ -33,13 +33,13 @@ } }, { - "key": "updateOnCorrelate", + "key": "onlyUpdateOnCorrelate", "type": "checkbox", "defaultValue": false, "templateOptions": { - "label": "Update user when correlating and mapped data differs from data in RAET IAM API", + "label": "Only update on correlate", "required": false, - "description": "This will update RAET IAM API users in the create action (not just correlate)." + "description": "When toggled, the Raet Beaufort user will only be updated when the account is correlated" } }, { diff --git a/create.ps1 b/create.ps1 index bc09f6e..236abcc 100644 --- a/create.ps1 +++ b/create.ps1 @@ -1,48 +1,20 @@ -##################################################### -# HelloID-Conn-Prov-Source-RAET-IAM-API-User-Create -# -# Version: 1.1.2 -##################################################### -# Initialize default values -$c = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$success = $false -$auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() - -# Set TLS to accept TLS, TLS 1.1 and TLS 1.2 -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 +################################################# +# HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity-Create +# PowerShell V2 +################################################# + +# Enable TLS1.2 +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 # Set debug logging -switch ($($c.isDebug)) { +switch ($($actionContext.Configuration.isDebug)) { $true { $VerbosePreference = 'Continue' } $false { $VerbosePreference = 'SilentlyContinue' } } -$InformationPreference = "Continue" -$WarningPreference = "Continue" # Used to connect to RAET IAM API endpoints -$clientId = $c.clientid -$clientSecret = $c.clientsecret -$TenantId = $c.tenantid -$updateOnCorrelate = $c.updateOnCorrelate - -$Script:AuthenticationUrl = "https://connect.visma.com/connect/token" -$Script:BaseUrl = "https://api.youforce.com" - -#Change mapping here -$account = [PSCustomObject]@{ - displayName = $p.DisplayName - externalId = $p.externalID - identity = $p.Accounts.MicrosoftActiveDirectory.userPrincipalName -} - -# # Troubleshooting -# $account = [PSCustomObject]@{ -# displayName = 'John Doe - Test (918030)' -# externalId = '999999' -# identity = 'j.doe@enyoi.org' -# } -# $dryRun = $false +$Script:AuthenticationUri = "https://connect.visma.com/connect/token" +$Script:BaseUri = "https://api.youforce.com" #region functions function Resolve-HTTPError { @@ -107,7 +79,7 @@ function New-RaetSession { 'tenant_id' = $TenantId } $splatAccessTokenParams = @{ - Uri = $AuthenticationUrl + Uri = $Script:AuthenticationUri Headers = @{'Cache-Control' = "no-cache" } Method = 'POST' ContentType = "application/x-www-form-urlencoded" @@ -169,52 +141,86 @@ function Confirm-AccessTokenIsValid { } #endregion functions -# Get current RAET user and verify if a user must be either [updated and correlated] or just [correlated] try { - Write-Verbose "Querying RAET user with employeeId '$($account.externalID)'" + # AccountReference must have a value + $outputContext.AccountReference = 'Currently not available' + + # To make sure a action is always send + $action = 'CreateAccount' + + # Validate correlation configuration + if ($actionContext.CorrelationConfiguration.Enabled) { + $correlationField = $actionContext.CorrelationConfiguration.PersonField + $correlationValue = $actionContext.CorrelationConfiguration.PersonFieldValue + + if ([string]::IsNullOrEmpty($($correlationField))) { + throw 'Correlation is enabled but not configured correctly' + } + if ([string]::IsNullOrEmpty($($correlationValue))) { + throw 'Correlation is enabled but [accountFieldValue] is empty. Please make sure it is correctly mapped' + } + } + else { + throw 'Enabling correlation is mandatory' + } $accessTokenValid = Confirm-AccessTokenIsValid if ($true -ne $accessTokenValid) { - New-RaetSession -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId + $splatRaetSession = @{ + ClientId = $actionContext.Configuration.clientId + ClientSecret = $actionContext.Configuration.clientSecret + TenantId = $actionContext.Configuration.tenantId + } + New-RaetSession @splatRaetSession } - $splatGetDataParams = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))" + Write-Verbose "Querying Raet Beaufort user with $($correlationField) '$($correlationValue)'" + + $splatWebRequest = @{ + Uri = "$($Script:BaseUri)/iam/v1.0/users(employeeId=$($correlationValue))" Headers = $Script:AuthenticationHeaders Method = 'GET' ContentType = "application/json" UseBasicParsing = $true } + $correlatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false - Write-Verbose "Querying data from '$($splatGetDataParams.Uri)'" - - $currentAccount = Invoke-RestMethod @splatGetDataParams -Verbose:$false + if ($null -eq $correlatedAccount.id) { + throw "No user found in Raet Beaufort with $($correlationField) '$($correlationValue)'" + } + else { + $action = 'CorrelateAccount' + $outputContext.AccountReference = $correlationValue + } - if ($null -eq $currentAccount.id) { - throw "No RAET user found with employeeId '$($account.externalID)'" + # Add a message and the result of each of the validations showing what will happen during enforcement + if ($actionContext.DryRun -eq $true) { + Write-Verbose "[DryRun] $action Raet Beaufort user account for: [$($personContext.Person.DisplayName)], will be executed during enforcement" -Verbose } - if ($updateOnCorrelate -eq $true) { - $action = 'Update-Correlate' + # Process + if (-not($actionContext.DryRun -eq $true)) { + switch ($action) { + 'CorrelateAccount' { + Write-Verbose 'Correlating Raet Beaufort employee account' - $propertiesChanged = $null - # Check if current Identity has a different value from mapped value. RAET IAM API will throw an error when trying to update this with the same value - if ([string]$currentAccount.identityId -ne $account.identity -and $null -ne $account.identity) { - $propertiesChanged += @('Identity') - } - if ($propertiesChanged) { - Write-Verbose "Account property(s) required to update: [$($propertiesChanged -join ",")]" - $updateAction = 'Update' - } - else { - $updateAction = 'NoChanges' + $outputContext.Data = $correlatedAccount + $outputContext.AccountCorrelated = $true + $auditLogMessage = "Correlated account: [$($correlatedAccount.id)] on field: [$($correlationField)] with value: [$($correlationValue)]" + break + } } - } - else { - $action = 'Correlate' + + $outputContext.success = $true + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Action = $action + Message = $auditLogMessage + IsError = $false + }) } } catch { + $outputContext.success = $false $ex = $PSItem if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { $errorObject = Resolve-HTTPError -Error $ex @@ -231,157 +237,11 @@ catch { if ([String]::IsNullOrEmpty($auditErrorMessage)) { $auditErrorMessage = $ex.Exception.Message } - + Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - if ($auditErrorMessage -Like "No RAET user found with employeeId '$($account.externalID)'") { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "No RAET user found with employeeId '$($account.externalID)'. Possibly deleted." - IsError = $true - }) - Write-Warning "DryRun: No RAET user found with employeeId '$($account.externalID)'. Possibly deleted." - } - else { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "Error querying RAET user with employeeId '$($account.externalID)'. Error Message: $auditErrorMessage" - IsError = $True - }) - } -} - -if ($null -ne $currentAccount.id) { - switch ($action) { - 'Update-Correlate' { - Write-Verbose "Updating and correlating RAET user with employeeId '$($account.externalID)'" - - switch ($updateAction) { - 'Update' { - try { - $updateAccount = [PSCustomObject]@{ - id = $account.identity - } - $body = ($updateAccount | ConvertTo-Json -Depth 10) - - $splatWebRequest = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))/identity" - Headers = $Script:AuthenticationHeaders - Method = 'PATCH' - Body = ([System.Text.Encoding]::UTF8.GetBytes($body)) - ContentType = "application/json;charset=utf-8" - UseBasicParsing = $true - } - - if (-not($dryRun -eq $true)) { - Write-Verbose "Updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - - $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false - # Set aRef object for use in futher actions - $aRef = $account.externalID - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'" - IsError = $false - }) - } - else { - Write-Warning "DryRun: Would update RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - } - break - } - catch { - $ex = $PSItem - if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { - $errorObject = Resolve-HTTPError -Error $ex - - $verboseErrorMessage = $errorObject.ErrorMessage - - $auditErrorMessage = $errorObject.ErrorMessage - } - - # If error message empty, fall back on $ex.Exception.Message - if ([String]::IsNullOrEmpty($verboseErrorMessage)) { - $verboseErrorMessage = $ex.Exception.Message - } - if ([String]::IsNullOrEmpty($auditErrorMessage)) { - $auditErrorMessage = $ex.Exception.Message - } - - $ex = $PSItem - $verboseErrorMessage = $ex - - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "Error updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity). Error Message: $auditErrorMessage" - IsError = $True - }) - } - } - 'NoChanges' { - Write-Verbose "No changes to RAET user with employeeId '$($account.externalID)'" - - if (-not($dryRun -eq $true)) { - # Set aRef object for use in futher actions - $aRef = $account.externalID - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'. (No Changes needed)" - IsError = $false - }) - } - else { - Write-Warning "DryRun: No changes to RAET user with employeeId '$($account.externalID)'" - } - break - } - } - break - } - 'Correlate' { - Write-Verbose "Correlating RAET user with employeeId '$($account.externalID)'" - - if (-not($dryRun -eq $true)) { - # Set aRef object for use in futher actions - $aRef = $account.externalID - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "CreateAccount" - Message = "Successfully correlated RAET user with employeeId '$($account.externalID)'" - IsError = $false - }) - } - else { - Write-Warning "DryRun: Would correlate RAET user with employeeId '$($account.externalID)'" - } - break - } - } -} - -#build up result -$result = [PSCustomObject]@{ - Success = $success - AccountReference = $aRef - AuditLogs = $auditLogs - Account = $account - - # Optionally return data for use in other systems - ExportData = [PSCustomObject]@{ - displayName = $account.displayName - identity = $account.identity - externalId = $account.externalId - } -} -#send result back -Write-Output $result | ConvertTo-Json -Depth 10 \ No newline at end of file + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = "Error updating Raet Beaufort employee with $($correlationProperty) '$($correlationValue)'. Error Message: $($auditErrorMessage)" + IsError = $true + }) +} \ No newline at end of file diff --git a/delete.ps1 b/delete.ps1 index 2c10232..565a2c7 100644 --- a/delete.ps1 +++ b/delete.ps1 @@ -1,49 +1,21 @@ -##################################################### -# HelloID-Conn-Prov-Source-RAET-IAM-API-User-Delete -# -# Version: 1.1.2 -##################################################### -# Initialize default values -$c = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$aref = $accountReference | ConvertFrom-Json -$success = $false -$auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() - -# Set TLS to accept TLS, TLS 1.1 and TLS 1.2 -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 +################################################## +# HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity-Delete +# PowerShell V2 +################################################## + +# Enable TLS1.2 +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 # Set debug logging -switch ($($c.isDebug)) { +switch ($($actionContext.Configuration.isDebug)) { $true { $VerbosePreference = 'Continue' } $false { $VerbosePreference = 'SilentlyContinue' } } -$InformationPreference = "Continue" -$WarningPreference = "Continue" # Used to connect to RAET IAM API endpoints -$clientId = $c.clientid -$clientSecret = $c.clientsecret -$TenantId = $c.tenantid - $Script:AuthenticationUrl = "https://connect.visma.com/connect/token" $Script:BaseUrl = "https://api.youforce.com" -#Change mapping here -$account = [PSCustomObject]@{ - displayName = $p.DisplayName - externalId = $aRef - identity = "" # empty because at the revoke action we want to clear the unique fields -} - -# # Troubleshooting -# $account = [PSCustomObject]@{ -# displayName = 'John Doe - Test (918030)' -# externalId = '999999' -# identity = "" # empty because at the revoke action we want to clear the unique fields -# } -# $dryRun = $false - #region functions function Resolve-HTTPError { [CmdletBinding()] @@ -107,7 +79,7 @@ function New-RaetSession { 'tenant_id' = $TenantId } $splatAccessTokenParams = @{ - Uri = $AuthenticationUrl + Uri = $Script:AuthenticationUrl Headers = @{'Cache-Control' = "no-cache" } Method = 'POST' ContentType = "application/x-www-form-urlencoded" @@ -169,182 +141,144 @@ function Confirm-AccessTokenIsValid { } #endregion functions -# Get current RAET user try { - Write-Verbose "Querying RAET user with employeeId '$($account.externalID)'" + # Verify if [aRef] has a value + if ([string]::IsNullOrEmpty($($actionContext.References.Account))) { + throw 'The account reference could not be found' + } + + $account = $actionContext.Data $accessTokenValid = Confirm-AccessTokenIsValid if ($true -ne $accessTokenValid) { - New-RaetSession -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId + $splatRaetSession = @{ + ClientId = $actionContext.Configuration.clientId + ClientSecret = $actionContext.Configuration.clientSecret + TenantId = $actionContext.Configuration.tenantId + } + New-RaetSession @splatRaetSession } - $splatGetDataParams = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))" + Write-Verbose "Verifying if a Raet Beaufort user account for [$($personContext.Person.DisplayName)] exists" + + $splatWebRequest = @{ + Uri = "$($Script:BaseUrl)/iam/v1.0/users(employeeId=$($actionContext.References.Account))" Headers = $Script:AuthenticationHeaders Method = 'GET' ContentType = "application/json" UseBasicParsing = $true } - - Write-Verbose "Querying data from '$($splatGetDataParams.Uri)'" - - $currentAccount = Invoke-RestMethod @splatGetDataParams -Verbose:$false - - if ($null -eq $currentAccount.id) { - throw "No RAET user with employeeId '$($account.externalID)'" - } - - $propertiesChanged = $null - # Check if current Identity has a different value from mapped value. RAET IAM API will throw an error when trying to update this with the same value - if ([string]$currentAccount.identityId -ne $account.identity -and $null -ne $account.identity) { - $propertiesChanged += @('Identity') - } - if ($propertiesChanged) { - Write-Verbose "Account property(s) required to update: [$($propertiesChanged -join ",")]" - $updateAction = 'Update' + $correlatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false + $outputContext.PreviousData.id = $correlatedAccount.id + $outputContext.PreviousData.identity = $correlatedAccount.identityId + # Patch to API has no response so we give back the mapping value to HelloID + $outputContext.Data.id = $correlatedAccount.id + $outputContext.Data.identity = $account.identity + + # Always compare the account against the current account in target system + if ($null -ne $correlatedAccount.id) { + if ([string]$correlatedAccount.identityId -ne $account.identity -and $null -ne $account.identity) { + $propertiesChanged += @('Identity') + } + if ($propertiesChanged) { + $updateAction = 'Update' + $dryRunMessage = "Would update RAET user with employeeId '$($actionContext.References.Account)'. Current identity: $($correlatedAccount.identityId). New identity: $($account.identity)" + } + else { + $updateAction = 'NoChanges' + $dryRunMessage = 'No changes will be made to the account during enforcement' + } + $action = 'DeleteAccount' } else { - $updateAction = 'NoChanges' + $updateAction = 'NotFound' + $dryRunMessage = "Raet Beaufort user account: [$($actionContext.References.Account)] for person: [$($personContext.Person.DisplayName)] could not be found, possibly indicating that it could be deleted, or the account is not correlated" } -} -catch { - $ex = $PSItem - if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { - $errorObject = Resolve-HTTPError -Error $ex - - $verboseErrorMessage = $errorObject.ErrorMessage - $auditErrorMessage = $errorObject.ErrorMessage + # Add a message and the result of each of the validations showing what will happen during enforcement + if ($actionContext.DryRun -eq $true) { + Write-Verbose "[DryRun] $dryRunMessage" -Verbose } - # If error message empty, fall back on $ex.Exception.Message - if ([String]::IsNullOrEmpty($verboseErrorMessage)) { - $verboseErrorMessage = $ex.Exception.Message - } - if ([String]::IsNullOrEmpty($auditErrorMessage)) { - $auditErrorMessage = $ex.Exception.Message - } - - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - - if ($auditErrorMessage -Like "No RAET user found with employeeId '$($account.externalID)'") { - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "DeleteAccount" - Message = "No RAET user found with employeeId '$($account.externalID)'. Possibly already deleted, skipping action." - IsError = $false - }) - Write-Warning "DryRun: No RAET user found with employeeId '$($account.externalID)'. Possibly already deleted, skipping action." - } - else { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "DeleteAccount" - Message = "Error querying RAET user found with employeeId '$($account.externalID)'. Error Message: $auditErrorMessage" - IsError = $True - }) - } -} + # Process + if (-not($actionContext.DryRun -eq $true)) { + switch ($updateAction) { + 'Update' { + Write-Verbose "Updating RAET user with employeeId '$($account.externalID)'. Current identity: $($correlatedAccount.identityId). New identity: $($account.identity)" -if ($null -ne $currentAccount.id) { - switch ($updateAction) { - 'Update' { - try { + # Some what confusing that the GET gives back a 'id' (aRef) and you have to PATCH the UPN on 'id' a well. This needs to be hardcoded $updateAccount = [PSCustomObject]@{ id = $account.identity } - $body = ($updateAccount | ConvertTo-Json -Depth 10) + $body = ($updateAccount | ConvertTo-Json -Depth 10) $splatWebRequest = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))/identity" + Uri = "$($Script:BaseUrl)/iam/v1.0/users(employeeId=$($actionContext.References.Account))/identity" Headers = $Script:AuthenticationHeaders Method = 'PATCH' Body = ([System.Text.Encoding]::UTF8.GetBytes($body)) ContentType = "application/json;charset=utf-8" UseBasicParsing = $true } - - if (-not($dryRun -eq $true)) { - Write-Verbose "Updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - - $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "DeleteAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'" - IsError = $false - }) - } - else { - Write-Warning "DryRun: Would update RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - } + + $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false + + $outputContext.Success = $true + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Action = $action + Message = "Update account was successful, current identity: [$($correlatedAccount.identityId)]. New identity: [$($account.identity)]" + IsError = $false + }) break } - catch { - $ex = $PSItem - if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { - $errorObject = Resolve-HTTPError -Error $ex - - $verboseErrorMessage = $errorObject.ErrorMessage - - $auditErrorMessage = $errorObject.ErrorMessage - } - - # If error message empty, fall back on $ex.Exception.Message - if ([String]::IsNullOrEmpty($verboseErrorMessage)) { - $verboseErrorMessage = $ex.Exception.Message - } - if ([String]::IsNullOrEmpty($auditErrorMessage)) { - $auditErrorMessage = $ex.Exception.Message - } - - $ex = $PSItem - $verboseErrorMessage = $ex - - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "DeleteAccount" - Message = "Error updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity). Error Message: $auditErrorMessage" - IsError = $True + + 'NoChanges' { + Write-Verbose "No changes to Raet Beaufort employee account with accountReference: [$($actionContext.References.Account)]" + + $outputContext.Success = $true + + # Remove this AuditLog? + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = 'No changes will be made to the account during enforcement' + IsError = $false }) + break } - } - 'NoChanges' { - Write-Verbose "No changes to RAET user with employeeId '$($account.externalID)'" - - if (-not($dryRun -eq $true)) { - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "DeleteAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'. (No Changes needed)" + + 'NotFound' { + $outputContext.Success = $true + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = "Raet Beaufort user account [$($actionContext.References.Account)] for: [$($personContext.Person.DisplayName)] could not be found, possibly indicating that it could be deleted, or the account is not correlated" IsError = $false }) + break } - else { - Write-Warning "DryRun: No changes to RAET user with employeeId '$($account.externalID)'" - } - break } } } - -#build up result -$result = [PSCustomObject]@{ - Success = $success - AccountReference = $aRef - AuditLogs = $auditLogs - Account = $account - PreviousAccount = $previousAccount - - # Optionally return data for use in other systems - ExportData = [PSCustomObject]@{ - displayName = $account.displayName - identity = $account.identity - externalId = $account.externalId +catch { + $outputContext.success = $false + $ex = $PSItem + if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { + $errorObject = Resolve-HTTPError -Error $ex + + $verboseErrorMessage = $errorObject.ErrorMessage + + $auditErrorMessage = $errorObject.ErrorMessage } -} -#send result back -Write-Output $result | ConvertTo-Json -Depth 10 \ No newline at end of file + + # If error message empty, fall back on $ex.Exception.Message + if ([String]::IsNullOrEmpty($verboseErrorMessage)) { + $verboseErrorMessage = $ex.Exception.Message + } + if ([String]::IsNullOrEmpty($auditErrorMessage)) { + $auditErrorMessage = $ex.Exception.Message + } + + Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" + + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = "Could not update Raet Beaufort user account. Error Message: $($auditErrorMessage)" + IsError = $true + }) +} \ No newline at end of file diff --git a/fieldMapping.json b/fieldMapping.json new file mode 100644 index 0000000..94b05a8 --- /dev/null +++ b/fieldMapping.json @@ -0,0 +1,50 @@ +{ + "Version": "v1", + "MappingFields": [ + { + "Name": "id", + "Description": "[Mandatory]", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create", + "Update", + "Delete" + ], + "MappingMode": "None", + "Value": "\"\"", + "UsedInNotifications": false, + "StoreInAccountData": true + } + ] + }, + { + "Name": "identity", + "Description": "[Mandatory]", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create", + "Update" + ], + "MappingMode": "Complex", + "Value": "\"function getUserPrincipalName() {\\r\\n let upn = '';\\r\\n\\r\\n if (typeof Person.Accounts.MicrosoftActiveDirectory.userPrincipalName !== 'undefined' && Person.Accounts.MicrosoftActiveDirectory.userPrincipalName) {\\r\\n upn = Person.Accounts.MicrosoftActiveDirectory.userPrincipalName;\\r\\n }\\r\\n\\r\\n return upn;\\r\\n}\\r\\n\\r\\ngetUserPrincipalName()\"", + "UsedInNotifications": false, + "StoreInAccountData": false + }, + { + "MapForActions": [ + "Delete" + ], + "MappingMode": "Complex", + "Value": "\"function getUserPrincipalName() {\\r\\n // If we return empty or \\\" \\\" UPN the Identity endpoint will return a Error.\\r\\n let prefix = Person.ExternalId;\\r\\n let domain = '@enjoy.com';\\r\\n let upn = prefix + domain;\\r\\n\\r\\n return upn;\\r\\n}\\r\\n\\r\\ngetUserPrincipalName()\"", + "UsedInNotifications": false, + "StoreInAccountData": false + } + ] + } + ], + "UniqueFieldNames": [] +} \ No newline at end of file diff --git a/update.ps1 b/update.ps1 index 044a89b..05a1508 100644 --- a/update.ps1 +++ b/update.ps1 @@ -1,49 +1,21 @@ -##################################################### -# HelloID-Conn-Prov-Source-RAET-IAM-API-User-Update -# -# Version: 1.1.2 -##################################################### -# Initialize default values -$c = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$aref = $accountReference | ConvertFrom-Json -$success = $false -$auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() - -# Set TLS to accept TLS, TLS 1.1 and TLS 1.2 -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 +################################################# +# HelloID-Conn-Prov-Target-Raet-Beaufort-IAM-API-Identity-Update +# PowerShell V2 +################################################# + +# Enable TLS1.2 +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 # Set debug logging -switch ($($c.isDebug)) { +switch ($($actionContext.Configuration.isDebug)) { $true { $VerbosePreference = 'Continue' } $false { $VerbosePreference = 'SilentlyContinue' } } -$InformationPreference = "Continue" -$WarningPreference = "Continue" # Used to connect to RAET IAM API endpoints -$clientId = $c.clientid -$clientSecret = $c.clientsecret -$TenantId = $c.tenantid - $Script:AuthenticationUrl = "https://connect.visma.com/connect/token" $Script:BaseUrl = "https://api.youforce.com" -#Change mapping here -$account = [PSCustomObject]@{ - displayName = $p.DisplayName - externalId = $aRef - identity = $p.Accounts.MicrosoftActiveDirectory.userPrincipalName -} - -# # Troubleshooting -# $account = [PSCustomObject]@{ -# displayName = 'John Doe - Test (918030)' -# externalId = '999999' -# identity = 'j.doe@enyoi.org' -# } -# $dryRun = $false - #region functions function Resolve-HTTPError { [CmdletBinding()] @@ -107,7 +79,7 @@ function New-RaetSession { 'tenant_id' = $TenantId } $splatAccessTokenParams = @{ - Uri = $AuthenticationUrl + Uri = $Script:AuthenticationUrl Headers = @{'Cache-Control' = "no-cache" } Method = 'POST' ContentType = "application/x-www-form-urlencoded" @@ -169,54 +141,139 @@ function Confirm-AccessTokenIsValid { } #endregion functions -# Get current RAET user try { - Write-Verbose "Querying RAET user with employeeId '$($account.externalID)'" + if (($actionContext.AccountCorrelated -eq $true) -or ($actionContext.Configuration.onlyUpdateOnCorrelate -eq $false)) { + + # Verify if [aRef] has a value + if ([string]::IsNullOrEmpty($($actionContext.References.Account))) { + throw 'The account reference could not be found' + } - $accessTokenValid = Confirm-AccessTokenIsValid - if ($true -ne $accessTokenValid) { - New-RaetSession -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId - } + $account = $actionContext.Data - $splatGetDataParams = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))" - Headers = $Script:AuthenticationHeaders - Method = 'GET' - ContentType = "application/json" - UseBasicParsing = $true - } + $accessTokenValid = Confirm-AccessTokenIsValid + if ($true -ne $accessTokenValid) { + $splatRaetSession = @{ + ClientId = $actionContext.Configuration.clientId + ClientSecret = $actionContext.Configuration.clientSecret + TenantId = $actionContext.Configuration.tenantId + } + New-RaetSession @splatRaetSession + } - Write-Verbose "Querying data from '$($splatGetDataParams.Uri)'" - $currentAccount = Invoke-RestMethod @splatGetDataParams -Verbose:$false + Write-Verbose "Verifying if a Raet Beaufort user account for [$($personContext.Person.DisplayName)] exists" - if ($null -eq $currentAccount.id) { - throw "No RAET user found with employeeId '$($account.externalID)'" - } + $splatWebRequest = @{ + Uri = "$($Script:BaseUrl)/iam/v1.0/users(employeeId=$($actionContext.References.Account))" + Headers = $Script:AuthenticationHeaders + Method = 'GET' + ContentType = "application/json" + UseBasicParsing = $true + } + $correlatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false + $outputContext.PreviousData.id = $correlatedAccount.id + $outputContext.PreviousData.identity = $correlatedAccount.identityId + # Patch to API has no response so we give back the mapping value to HelloID + $outputContext.Data.id = $correlatedAccount.id + $outputContext.Data.identity = $account.identity + + # Always compare the account against the current account in target system + if ($null -ne $correlatedAccount.id) { + if ([string]$correlatedAccount.identityId -ne $account.identity -and $null -ne $account.identity) { + $propertiesChanged += @('Identity') + } + if ($propertiesChanged) { + $action = 'UpdateAccount' + $dryRunMessage = "Would update RAET user with employeeId '$($actionContext.References.Account)'. Current identity: $($correlatedAccount.identityId). New identity: $($account.identity)" + } + else { + $action = 'NoChanges' + $dryRunMessage = 'No changes will be made to the account during enforcement' + } + } + else { + $action = 'NotFound' + $dryRunMessage = "Raet Beaufort employee account for: [$($personContext.Person.DisplayName)] not found. Possibly deleted." + } - $propertiesChanged = $null - # Check if current Identity has a different value from mapped value. RAET IAM API will throw an error when trying to update this with the same value - if ([string]$currentAccount.identityId -ne $account.identity -and $null -ne $account.identity) { - $propertiesChanged += @('Identity') - } - if ($propertiesChanged) { - Write-Verbose "Account property(s) required to update: [$($propertiesChanged -join ",")]" - $updateAction = 'Update' + # Add a message and the result of each of the validations showing what will happen during enforcement + if ($actionContext.DryRun -eq $true) { + Write-Verbose "[DryRun] $dryRunMessage" -Verbose + } + + # Process + if (-not($actionContext.DryRun -eq $true)) { + switch ($action) { + 'UpdateAccount' { + Write-Verbose "Updating RAET user with employeeId '$($account.externalID)'. Current identity: $($correlatedAccount.identityId). New identity: $($account.identity)" + + # Some what confusing that the GET gives back a 'id' (aRef) and you have to PATCH the UPN on 'id' a well. This needs to be hardcoded + $updateAccount = [PSCustomObject]@{ + id = $account.identity + } + $body = ($updateAccount | ConvertTo-Json -Depth 10) + + $splatWebRequest = @{ + Uri = "$($Script:BaseUrl)/iam/v1.0/users(employeeId=$($actionContext.References.Account))/identity" + Headers = $Script:AuthenticationHeaders + Method = 'PATCH' + Body = ([System.Text.Encoding]::UTF8.GetBytes($body)) + ContentType = "application/json;charset=utf-8" + UseBasicParsing = $true + } + + $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false + + $outputContext.Success = $true + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Action = $action + Message = "Update account was successful, current identity: [$($correlatedAccount.identityId)]. New identity: [$($account.identity)]" + IsError = $false + }) + break + } + + 'NoChanges' { + Write-Verbose "No changes to Raet Beaufort user account with accountReference: [$($actionContext.References.Account)]" + + $outputContext.Success = $true + + # Remove this AuditLog? + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = 'No changes will be made to the account during enforcement' + IsError = $false + }) + break + } + + 'NotFound' { + $outputContext.Success = $false + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = "Raet Beaufort user account [$($actionContext.References.Account)] for: [$($personContext.Person.DisplayName)] could not be found, possibly indicating that it could be deleted, or the account is not correlated" + IsError = $true + }) + break + } + } + } } else { - $updateAction = 'NoChanges' + Write-Verbose "The configuration parameter only update on correlate is [$($actionContext.Configuration.onlyUpdateOnCorrelate)]" + $outputContext.Success = $true } } catch { + $outputContext.success = $false $ex = $PSItem if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { $errorObject = Resolve-HTTPError -Error $ex - + $verboseErrorMessage = $errorObject.ErrorMessage - + $auditErrorMessage = $errorObject.ErrorMessage } - + # If error message empty, fall back on $ex.Exception.Message if ([String]::IsNullOrEmpty($verboseErrorMessage)) { $verboseErrorMessage = $ex.Exception.Message @@ -224,127 +281,11 @@ catch { if ([String]::IsNullOrEmpty($auditErrorMessage)) { $auditErrorMessage = $ex.Exception.Message } - + Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - - if ($auditErrorMessage -Like "No RAET user found with employeeId '$($account.externalID)'") { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "UpdateAccount" - Message = "No RAET user found with employeeId '$($account.externalID)'. Possibly deleted." - IsError = $true - }) - Write-Warning "DryRun: No RAET user found with employeeId '$($account.externalID)'. Possibly deleted." - } - else { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "UpdateAccount" - Message = "Error querying RAET user with employeeId '$($account.externalID)'. Error Message: $auditErrorMessage" - IsError = $True - }) - } -} - -if ($null -ne $currentAccount.id) { - switch ($updateAction) { - 'Update' { - try { - $updateAccount = [PSCustomObject]@{ - id = $account.identity - } - $body = ($updateAccount | ConvertTo-Json -Depth 10) - - $splatWebRequest = @{ - Uri = "$baseUrl/iam/v1.0/users(employeeId=$($account.externalID))/identity" - Headers = $Script:AuthenticationHeaders - Method = 'PATCH' - Body = ([System.Text.Encoding]::UTF8.GetBytes($body)) - ContentType = "application/json;charset=utf-8" - UseBasicParsing = $true - } - - if (-not($dryRun -eq $true)) { - Write-Verbose "Updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - - $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "UpdateAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'" - IsError = $false - }) - } - else { - Write-Warning "DryRun: Would update RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity)" - } - break - } - catch { - $ex = $PSItem - if ( $($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { - $errorObject = Resolve-HTTPError -Error $ex - - $verboseErrorMessage = $errorObject.ErrorMessage - - $auditErrorMessage = $errorObject.ErrorMessage - } - - # If error message empty, fall back on $ex.Exception.Message - if ([String]::IsNullOrEmpty($verboseErrorMessage)) { - $verboseErrorMessage = $ex.Exception.Message - } - if ([String]::IsNullOrEmpty($auditErrorMessage)) { - $auditErrorMessage = $ex.Exception.Message - } - - $ex = $PSItem - $verboseErrorMessage = $ex - - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($verboseErrorMessage)" - - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Action = "UpdateAccount" - Message = "Error updating RAET user with employeeId '$($account.externalID)'. Current identity: $($currentAccount.identityId). New identity: $($account.identity). Error Message: $auditErrorMessage" - IsError = $True - }) - } - } - 'NoChanges' { - Write-Verbose "No changes to RAET user with employeeId '$($account.externalID)'" - - if (-not($dryRun -eq $true)) { - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Action = "UpdateAccount" - Message = "Successfully updated RAET user with employeeId '$($account.externalID)'. (No Changes needed)" - IsError = $false - }) - } - else { - Write-Warning "DryRun: No changes to RAET user with employeeId '$($account.externalID)'" - } - break - } - } -} - -#build up result -$result = [PSCustomObject]@{ - Success = $success - AccountReference = $aRef - AuditLogs = $auditLogs - Account = $account - PreviousAccount = $previousAccount - - # Optionally return data for use in other systems - ExportData = [PSCustomObject]@{ - displayName = $account.displayName - identity = $account.identity - externalId = $account.externalId - } -} -#send result back -Write-Output $result | ConvertTo-Json -Depth 10 \ No newline at end of file + + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Message = "Could not update Raet Beaufort user account. Error Message: $($auditErrorMessage)" + IsError = $true + }) +} \ No newline at end of file