From 3795d7b40986523a5c6122c80ca763ba121f4469 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Tue, 13 May 2025 09:42:52 -0500 Subject: [PATCH] Updates user profile picture function Refactors the Set-VerkadaAccessUserProfilePicture function to use the public API. This change updates the function to utilize the public API for setting user profile pictures, enhancing functionality and security. It replaces the internal API call with a call to https://apidocs.verkada.com/reference/putprofilephotoviewv1, adding support for externalId. It also adds support for selecting which region of the public API to use. --- .../Private/Invoke-VerkadaFormCall.ps1 | 37 ++++++-- .../Set-VerkadaAccessUserProfilePicture.ps1 | 89 ++++++++++++------- 2 files changed, 85 insertions(+), 41 deletions(-) diff --git a/verkadaModule/Private/Invoke-VerkadaFormCall.ps1 b/verkadaModule/Private/Invoke-VerkadaFormCall.ps1 index 5d8d9b7..6a240d0 100644 --- a/verkadaModule/Private/Invoke-VerkadaFormCall.ps1 +++ b/verkadaModule/Private/Invoke-VerkadaFormCall.ps1 @@ -7,7 +7,7 @@ function Invoke-VerkadaFormCall Private function to build Invoke-RestMethod calls for Verkada's private API enpoints that require a form #> - [CmdletBinding(PositionalBinding = $true)] + [CmdletBinding(PositionalBinding = $true, DefaultParameterSetName = 'Default')] Param( #The url for the enpoint to be used [Parameter(Mandatory = $true, Position = 0)] @@ -20,24 +20,47 @@ function Invoke-VerkadaFormCall #Object to pass form parameters to forms [Parameter(Mandatory = $true,Position = 2)] [Object]$form_params, + #Object containing the query parameters need that will be put into the query string of the uri + [Parameter()] + [Object]$query_params, #HTTP method required [Parameter()] [String]$method = 'POST', #The Verkada(CSRF) token of the user running the command - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'UnPwd')] [ValidateNotNullOrEmpty()] [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$')] [string]$x_verkada_token, #The Verkada Auth(session auth) token of the user running the command - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'UnPwd')] [ValidateNotNullOrEmpty()] - [string]$x_verkada_auth + [string]$x_verkada_auth, + #The public API token obatined via the Login endpoint to be used for calls that hit the public API gateway + [Parameter(ParameterSetName = 'Default')] + [String]$x_verkada_auth_api ) Process { - $headers=@{ - 'x-verkada-token' = $x_verkada_token - 'X-Verkada-Auth' = $x_verkada_auth + if ($PSCmdlet.ParameterSetName -eq 'UnPwd'){ + $headers=@{ + 'x-verkada-token' = $x_verkada_token + 'X-Verkada-Auth' = $x_verkada_auth + } + } else { + $headers=@{ + 'x-verkada-auth' = $x_verkada_auth_api + } + } + + if($query_params){ + $query = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) + foreach ($qp in $query_params.GetEnumerator()) { + $query.add("$($qp.name)", "$($qp.value)") + } + $uri = [System.UriBuilder]"$url" + $uri.Query = $query.ToString() + $uri = $uri.Uri.OriginalString + $url = $uri } $uri = $url diff --git a/verkadaModule/Public/Set-VerkadaAccessUserProfilePicture.ps1 b/verkadaModule/Public/Set-VerkadaAccessUserProfilePicture.ps1 index 0acdc43..74b0914 100644 --- a/verkadaModule/Public/Set-VerkadaAccessUserProfilePicture.ps1 +++ b/verkadaModule/Public/Set-VerkadaAccessUserProfilePicture.ps1 @@ -1,11 +1,11 @@ function Set-VerkadaAccessUserProfilePicture{ <# .SYNOPSIS - Adds/replaces an Access user's profile picture in an organization. + Adds/replaces an Access user's profile picture in an organization using https://apidocs.verkada.com/reference/putprofilephotoviewv1 .DESCRIPTION - This will set the Access user's, specified by the userId, profile picture. This must be a png or jpeg/jpg format image. - The org_id and reqired tokens can be directly submitted as parameters, but is much easier to use Connect-Verkada to cache this information ahead of time and for subsequent commands. + This will set the Access user's, specified by the user_Id or external_ID, profile picture. This must be a png or jpeg/jpg format image. + The org_id and reqired token can be directly submitted as parameters, but is much easier to use Connect-Verkada to cache this information ahead of time and for subsequent commands. .LINK https://github.com/bepsoccer/verkadaModule/blob/master/docs/function-documentation/Set-VerkadaAccessUserProfilePicture.md @@ -15,8 +15,8 @@ function Set-VerkadaAccessUserProfilePicture{ This sets the Access user with userId 801c9551-b04c-4293-84ad-b0a6aa0588b3 to use the picture specified at path ./myPicture.jpg. The org_id and tokens will be populated from the cached created by Connect-Verkada. .EXAMPLE - Set-VerkadaAccessUserProfilePicture -userId '801c9551-b04c-4293-84ad-b0a6aa0588b3' -imagePath './myPicture.png' -org_id '7cd47706-f51b-4419-8675-3b9f0ce7c12d' -x_verkada_token 'a366ef47-2c20-4d35-a90a-10fd2aee113a' -x_verkada_auth 'auth-token-uuid-dscsdc' -usr 'a099bfe6-34ff-4976-9d53-ac68342d2b60' - This sets the Access user with userId 801c9551-b04c-4293-84ad-b0a6aa0588b3 to use the picture specified at path ./myPicture.png. The org_id and tokens are submitted as parameters in the call. + Set-VerkadaAccessUserProfilePicture -externalId 'newUserUPN@contoso.com' -imagePath './myPicture.png' -org_id '7cd47706-f51b-4419-8675-3b9f0ce7c12d' -x_verkada_auth_api 'sd78ds-uuid-of-verkada-token' + This sets the Access user with externalId newUserUPN@contoso.com to use the picture specified at path ./myPicture.png. The org_id and tokens are submitted as parameters in the call. #> [CmdletBinding(PositionalBinding = $true)] [Alias("Set-VrkdaAcUsrPrflPic","s-VrkdaAcUsrPrflPic")] @@ -27,72 +27,93 @@ function Set-VerkadaAccessUserProfilePicture{ [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$')] [Alias('user_id')] [String]$userId, + #unique identifier managed externally provided by the consumer + [Parameter(ValueFromPipelineByPropertyName = $true)] + [Alias('external_id')] + [String]$externalId, #This is the path the image will be uploaded from [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [ValidatePattern('^.*\.(jpg|jpeg|png)$')] [string]$imagePath, + #The flag that states whether to overwrite the existing profile photo + [Parameter(ValueFromPipelineByPropertyName = $true)] + [bool]$overwrite=$false, #The UUID of the organization the user belongs to [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$')] [String]$org_id = $Global:verkadaConnection.org_id, - #The Verkada(CSRF) token of the user running the command + #The public API token obatined via the Login endpoint to be used for calls that hit the public API gateway [Parameter()] [ValidateNotNullOrEmpty()] - [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$')] - [string]$x_verkada_token = $Global:verkadaConnection.csrfToken, - #The Verkada Auth(session auth) token of the user running the command + [String]$x_verkada_auth_api = $Global:verkadaConnection.x_verkada_auth_api, + #The region of the public API to be used [Parameter()] - [ValidateNotNullOrEmpty()] - [string]$x_verkada_auth = $Global:verkadaConnection.userToken, - #The UUID of the user account making the request + [ValidateSet('api','api.eu','api.au')] + [String]$region='api', + #Switch to write errors to file [Parameter()] - [ValidateNotNullOrEmpty()] - [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$')] - [string]$usr = $Global:verkadaConnection.usr + [switch]$errorsToFile ) begin { + $url = "https://$($region).verkada.com/access/v1/access_users/user/profile_photo" #parameter validation if ([string]::IsNullOrEmpty($org_id)) {throw "org_id is missing but is required!"} - if ([string]::IsNullOrEmpty($x_verkada_token)) {throw "x_verkada_token is missing but is required!"} - if ([string]::IsNullOrEmpty($x_verkada_auth)) {throw "x_verkada_auth is missing but is required!"} - if ([string]::IsNullOrEmpty($usr)) {throw "usr is missing but is required!"} - - $url = 'https://vcerberus.command.verkada.com/user/photos/upload' + if ([string]::IsNullOrEmpty($x_verkada_auth_api)) {throw "x_verkada_auth_api is missing but is required!"} + $myErrors = @() } #end begin process { - if (!(Test-Path -Path $imagePath -PathType Leaf)){ - Write-Error "$imagePath is not a valid file path" - return - } - if ([string]::IsNullOrEmpty($userId)){ - Write-Error "userId required" + if ([string]::IsNullOrEmpty($externalId) -and [string]::IsNullOrEmpty($userId)){ + Write-Error "Either externalId or userId required" return } $form = @{ - organizationId = $org_id - userId = $userId - file = Get-Item -Path $imagePath + file = Get-Item -Path $imagePath + } + + $query_params = @{ + 'overwrite' = $overwrite } + if (!([string]::IsNullOrEmpty($userId))){ + $query_params.user_id = $userId + } elseif (!([string]::IsNullOrEmpty($externalId))){ + $query_params.external_id = $externalId + } + try { - Invoke-VerkadaFormCall $url $org_id $form -x_verkada_token $x_verkada_token -x_verkada_auth $x_verkada_auth -Method 'POST' + Invoke-VerkadaFormCall $url $org_id $form -query_params $query_params -x_verkada_auth_api $x_verkada_auth_api -Method 'PUT' + return "Successfully uploaded $imagePath to $($query_params | ConvertTo-Json -Compress)" } catch [Microsoft.PowerShell.Commands.HttpResponseException] { $err = $_.ErrorDetails | ConvertFrom-Json $errorMes = $_ | Convertto-Json -WarningAction SilentlyContinue $err | Add-Member -NotePropertyName StatusCode -NotePropertyValue (($errorMes | ConvertFrom-Json -Depth 100 -WarningAction SilentlyContinue).Exception.Response.StatusCode) -Force - - Write-Host "$($err.StatusCode) - $($err.message)" -ForegroundColor Red - Return + $msg = "$($err.StatusCode) - $($err.message)" + $msg += ": $(($query_params + $form) | ConvertTo-Json -Compress)" + Write-Error $msg + $myErrors += $msg + $msg = $null + } + catch [VerkadaRestMethodException] { + $msg = $_.ToString() + $msg += ": $(($query_params + $body_params) | ConvertTo-Json -Compress)" + Write-Error $msg + $myErrors += $msg + $msg = $null } } #end process end { - + if ($errorsToFile.IsPresent){ + if (![string]::IsNullOrEmpty($myErrors)){ + Get-Date | Out-File ./errors.txt -Append + $myErrors | Out-File ./errors.txt -Append + } + } } #end end } #end function \ No newline at end of file