From 1389fa75a96abe1b1be5c21619f0c07a47d8dfee Mon Sep 17 00:00:00 2001 From: Greg Brownstein Date: Sun, 22 Jun 2025 12:06:32 -0400 Subject: [PATCH 1/3] access token refresh --- .gitignore | 1 + RELEASE.md | 2 +- Readme.md | 2 +- ServiceNow/Private/Get-ServiceNowAuth.ps1 | 29 +++++++++++++++++++++ ServiceNow/Public/New-ServiceNowSession.ps1 | 8 +++++- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3031fa8..753d1c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.Pester.Defaults.json *.zip +.DS_Store \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md index a39ba9e..f219c72 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1 +1 @@ -- Fix invalid Id error, [#262](https://github.com/Snow-Shell/servicenow-powershell/issues/262) +- Add support for access token refresh, [#277](https://github.com/Snow-Shell/servicenow-powershell/issues/277) diff --git a/Readme.md b/Readme.md index 06c7257..9659ca2 100644 --- a/Readme.md +++ b/Readme.md @@ -43,7 +43,7 @@ $params = @{ New-ServiceNowSession @params ``` -Oauth authentication with user credential as well as application/client credential. The application/client credential can be found in the System OAuth->Application Registry section of ServiceNow. +Oauth authentication with user credential as well as application/client credential. The application/client credential can be found in the System OAuth->Application Registry section of ServiceNow. The access token will be refreshed automatically when it expires. ```PowerShell $params = @{ Url = 'instance.service-now.com' diff --git a/ServiceNow/Private/Get-ServiceNowAuth.ps1 b/ServiceNow/Private/Get-ServiceNowAuth.ps1 index 3bb0043..9f221a5 100644 --- a/ServiceNow/Private/Get-ServiceNowAuth.ps1 +++ b/ServiceNow/Private/Get-ServiceNowAuth.ps1 @@ -31,6 +31,35 @@ function Get-ServiceNowAuth { if ( $ServiceNowSession.Count -gt 0 ) { $hashOut.Uri = $ServiceNowSession.BaseUri + + # check if we need a new access token + if ( $ServiceNowSession.ExpiresOn -lt (Get-Date) -and $ServiceNowSession.RefreshToken -and $ServiceNowSession.ClientCredential ) { + # we've expired and have a refresh token + $refreshParams = @{ + Uri = 'https://{0}/oauth_token.do' -f $ServiceNowSession.Domain + Method = 'POST' + ContentType = 'application/x-www-form-urlencoded' + Body = @{ + grant_type = 'refresh_token' + client_id = $ServiceNowSession.ClientCredential.UserName + client_secret = $ServiceNowSession.ClientCredential.GetNetworkCredential().password + refresh_token = $ServiceNowSession.RefreshToken.GetNetworkCredential().password + } + } + + $response = Invoke-RestMethod @refreshParams + + $ServiceNowSession.AccessToken = New-Object System.Management.Automation.PSCredential('AccessToken', ($response.access_token | ConvertTo-SecureString -AsPlainText -Force)) + $ServiceNowSession.RefreshToken = New-Object System.Management.Automation.PSCredential('RefreshToken', ($response.refresh_token | ConvertTo-SecureString -AsPlainText -Force)) + if ($response.expires_in) { + $ServiceNowSession.ExpiresOn = (Get-Date).AddSeconds($response.expires_in) + Write-Verbose ('Access token has been refreshed and will expire at {0}' -f $ServiceNowSession.ExpiresOn) + } + + # ensure script/module scoped variable is updated + $script:ServiceNowSession = $ServiceNowSession + } + if ( $ServiceNowSession.AccessToken ) { $hashOut.Headers = @{ 'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken.GetNetworkCredential().password diff --git a/ServiceNow/Public/New-ServiceNowSession.ps1 b/ServiceNow/Public/New-ServiceNowSession.ps1 index ca99636..2434a93 100644 --- a/ServiceNow/Public/New-ServiceNowSession.ps1 +++ b/ServiceNow/Public/New-ServiceNowSession.ps1 @@ -6,8 +6,11 @@ Create a new ServiceNow session Create a new ServiceNow session via credentials, OAuth, or access token. This session will be used by default for all future calls. Optionally, you can specify the api version you'd like to use; the default is the latest. + To use OAuth, ensure you've set it up, https://docs.servicenow.com/bundle/quebec-platform-administration/page/administer/security/task/t_SettingUpOAuth.html. +If using OAuth, the client credential will be stored in the script scoped variable ServiceNowSession and the access token will be automatically refreshed. + .PARAMETER Url Base domain for your ServiceNow instance, eg. tenant.domain.com @@ -200,8 +203,11 @@ function New-ServiceNowSession { if ($token.expires_in) { $expiryTime = (Get-Date).AddSeconds($token.expires_in) $newSession.Add('ExpiresOn', $expiryTime) - Write-Verbose "Token will expire at $expiryTime" + Write-Verbose "Access token will expire at $expiryTime" } + # store client credential as it will be needed to refresh the access token + $newSession.Add('ClientCredential', $ClientCredential) + } else { # invoke-webrequest didn't throw an error, but we didn't get a token back either From 4ed74630e450bdd893ffbfa3167daf2d626e428f Mon Sep 17 00:00:00 2001 From: Greg Brownstein Date: Sun, 22 Jun 2025 12:09:38 -0400 Subject: [PATCH 2/3] update pipeline due to deprecation --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6be76d4..13ef6e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." } - name: Upload test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: pssa-results path: ${{ github.workspace }}/pssa.json From 5dde0c3b941b5861c994fc97a21c6d6318a5ef15 Mon Sep 17 00:00:00 2001 From: Greg Brownstein Date: Sun, 22 Jun 2025 12:18:49 -0400 Subject: [PATCH 3/3] version update --- ServiceNow/ServiceNow.psd1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceNow/ServiceNow.psd1 b/ServiceNow/ServiceNow.psd1 index ba174d2..f11515d 100644 --- a/ServiceNow/ServiceNow.psd1 +++ b/ServiceNow/ServiceNow.psd1 @@ -12,7 +12,7 @@ RootModule = 'ServiceNow.psm1' # Version number of this module. -ModuleVersion = '4.0.5' +ModuleVersion = '4.1' # Supported PSEditions # CompatiblePSEditions = @() @@ -27,7 +27,7 @@ Author = 'Greg Brownstein, Rick Arroues, Sam Martin' CompanyName = 'None' # Copyright statement for this module -Copyright = '(c) 2015-2023 Snow-Shell. All rights reserved.' +Copyright = '(c) 2015-2025 Snow-Shell. All rights reserved.' # Description of the functionality provided by this module Description = 'Automate against ServiceNow service and asset management. This module can be used standalone, with Azure Automation, or Docker.'