Skip to content

Commit

Permalink
New-TssSession - closes #69
Browse files Browse the repository at this point in the history
  • Loading branch information
wsmelton committed Feb 10, 2021
1 parent 66a74cd commit 9a983a3
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 88 deletions.
24 changes: 9 additions & 15 deletions src/classes/TssSession.class.ps1
Expand Up @@ -9,7 +9,6 @@
[int]$ExpiresIn
hidden [datetime]$TimeOfDeath
[int]$Take = [int]::MaxValue
hidden [boolean]$ExternalToken

[boolean]IsValidSession() {
if ([string]::IsNullOrEmpty($this.AccessToken) -and $this.StartTime -eq '0001-01-01 00:00:00') {
Expand All @@ -20,12 +19,12 @@
}

[boolean]IsValidToken() {
if ([string]::IsNullOrEmpty($this.AccessToken) -and (-not $this.ExternalToken)) {
if ([string]::IsNullOrEmpty($this.AccessToken)) {
Write-Host 'No valid token found for current TssSession object'
return $false
} elseif ([datetime]::Now -lt $this.TimeOfDeath -and (-not $this.ExternalToken)) {
} elseif ([datetime]::Now -lt $this.TimeOfDeath -and ($this.TokenType -ne 'ExternalToken')) {
return $true
} elseif ([datetime]::Now -gt $this.TimeOfDeath -and (-not $this.ExternalToken)) {
} elseif ([datetime]::Now -gt $this.TimeOfDeath -and ($this.TokenType -ne 'ExternalToken')) {
Write-Host 'Token is not valid and has exceeded TimeOfDeath'
return $false
} elseif ($this.ExternalToken) {
Expand All @@ -37,22 +36,17 @@
}

[boolean]SessionExpire() {
if (-not $this.ExternalToken) {
$url = $this.SecretServer, $this.ApiVersion, 'oauth-expiration' -join '/'
try {
Invoke-TssRestApi -Uri $url -Method Post -PersonalAccessToken $this.AccessToken
return $true
} catch {
return $false
}
} else {
Write-Warning 'Token was provided through external source and cannot be expired'
$url = $this.ApiUrl, 'oauth-expiration' -join '/'
try {
Invoke-TssRestApi -Uri $url -Method Post -PersonalAccessToken $this.AccessToken
return $true
} catch {
return $false
}
}

[boolean]SessionRefresh() {
if ($this.ExternalToken) {
if ($this.TokenType -eq 'ExternalToken') {
Write-Warning 'Token was provided through external source, SessionRefresh is not supported'
return $false
}
Expand Down
111 changes: 57 additions & 54 deletions src/functions/New-Session.ps1
Expand Up @@ -38,89 +38,92 @@
.OUTPUTS
TssSession.
#>
[cmdletbinding(SupportsShouldProcess)]
[cmdletbinding()]
[OutputType('TssSession')]
param(
# Secret Server URL
[Parameter(ParameterSetName = 'new',Mandatory)]
[Parameter(ParameterSetName = 'sdk',
Mandatory)]
[Alias('Server')]
[Parameter(ParameterSetName = 'new',Mandatory, Position = 0)]
[Parameter(ParameterSetName = 'sdk',Mandatory, Position = 0)]
[uri]
$SecretServer,

# Specify a Secret Server user account.
[Parameter(ParameterSetName = 'new')]
[Parameter(ParameterSetName = 'new', Mandatory)]
[PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,

# Specify Access Token
# Bypasses requesting a token from Secret Server
[Parameter(ParameterSetName = 'sdk')]
[Parameter(ParameterSetName = 'sdk', Mandatory)]
$AccessToken
)

begin {
$newTssParams = $PSBoundParameters
$invokeParams = @{ }

$outputTssSession = [TssSession]::new()

if ($SecretServer -match "(?:\/api\/v1)|(?:\/oauth2\/token)") {
throw "Invalid argument on parameter SecretServer. Please ensure [/api/v1] or [/oauth2/token] are not provided"
} else {
$outputTssSession.SecretServer = $SecretServer
$outputTssSession.ApiUrl = ($outputTssSession.SecretServer, $outputTssSession.ApiVersion -join '/')
}
}

process {
if (-not $newTssParams['AccessToken']) {
if ($newTssParams.ContainsKey('SecretServer')) {
$uri = $SecretServer, "oauth2/token" -join '/'
}

$postContent = [Ordered]@{ }
Write-Verbose "Provided command parameters: $(. $GetInvocation $PSCmdlet.MyInvocation)"

if ($outputTssSession.SecretServer) {
Write-Verbose "SecretServer host: $($outputTssSession.SecretServer)"
if ($newTssParams.ContainsKey('Credential')) {
$postContent.username = $Credential.UserName
$postContent.password = $Credential.GetNetworkCredential().Password
$postContent.grant_type = 'password'
}
$invokeParams.Uri = $SecretServer, 'oauth2/token' -join '/'

$invokeParams.Uri = $Uri
$invokeParams.Body = $postContent
$invokeParams.Method = 'POST'

if (-not $PSCmdlet.ShouldProcess("POST $uri")) { return }
try {
$restResponse = Invoke-TssRestApi @invokeParams -Property @{SecretServer = $SecretServer }
} catch {
Write-Warning "Issue authenticating to [$SecretServer]"
$err = $_.ErrorDetails.Message
Write-Error $err
}
$oauth2Body = [Ordered]@{ }
if ($newTssParams.ContainsKey('Credential')) {
$oauth2Body.username = $Credential.UserName
$oauth2Body.password = $Credential.GetNetworkCredential().Password
$oauth2Body.grant_type = 'password'
}

$invokeParams.Body = $oauth2Body
$invokeParams.Method = 'POST'

Write-Verbose "$($invokeParams.Method) $uri with:`t$($invokeParams.Body)`n"
try {
$restResponse = Invoke-TssRestApi @invokeParams
} catch {
Write-Warning "Issue authenticating to [$SecretServer]"
$err = $_.ErrorDetails.Message
if ($err.Length -gt 0) {
throw $err
} elseif ($_ -like '*<html*') {
$PSCmdlet.WriteError([Management.Automation.ErrorRecord]::new([Exception]::new("Response was HTML, Request Failed."),"ResultWasHTML", "NotSpecified", $invokeParams.Uri))
} else {
throw $_.Exception
}
}

if ($restResponse) {
$sessionObj = [TssSession]::new()
$sessionObj.SecretServer = $restResponse.SecretServer
$sessionObj.ApiUrl =
if ( ($restResponse.SecretServer).PathAndQuery -eq '/') {
[string]$restResponse.SecretServer + $sessionObj.ApiVersion
} elseif ( ($restResponse.SecretServer).PathAndQuery.Length -gt 1) {
[string]$restResponse.SecretServer, $sessionObj.ApiVersion -join '/'
} elseif ( ($restResponse.SecretServer).Segments -contains 'api/') {
[string]$restResponse.SecretServer
if ($restResponse) {
$outputTssSession.AccessToken = $restResponse.access_token
$outputTssSession.RefreshToken = $restResponse.refresh_token
$outputTssSession.ExpiresIn = $restResponse.expires_in
$outputTssSession.TokenType = $restResponse.token_type
$outputTssSession.TimeOfDeath = [datetime]::Now.Add([timespan]::FromSeconds($restResponse.expires_in))
}
$sessionObj.AccessToken = $restResponse.access_token
$sessionObj.RefreshToken = $restResponse.refresh_token
$sessionObj.ExpiresIn = $restResponse.expires_in
$sessionObj.TokenType = $restResponse.token_type
$sessionObj.StartTime = [datetime]::Now
$sessionObj.TimeOfDeath = [datetime]::Now.Add([timespan]::FromSeconds($restResponse.expires_in))

return $sessionObj
}
}
if ($newTssParams['SecretServer'] -and $newTssParams['AccessToken']) {
[TssSession]@{
SecretServer = $SecretServer
AccessToken = $AccessToken
StartTime = [datetime]::Now
ExternalToken = $true

if ($newTssParams.ContainsKey('AccessToken')) {
$outputTssSession.AccessToken = $AccessToken
$outputTssSession.TokenType = 'External'
}

$outputTssSession.StartTime = [datetime]::Now
return $outputTssSession
} else {
Write-Warning "SecretServer argument not found"
}
}
}
79 changes: 61 additions & 18 deletions tests/functions/New-TssSession.Tests.ps1
Expand Up @@ -6,54 +6,97 @@ Describe "$commandName verify parameters" {
BeforeDiscovery {
[object[]]$knownParameters = 'SecretServer', 'Credential', 'AccessToken'
[object[]]$currentParams = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($commandName, 'Function')).Parameters.Keys
[object[]]$commandDetails = [System.Management.Automation.CommandInfo]$ExecutionContext.SessionState.InvokeCommand.GetCommand($commandName,'Function')
$unknownParameters = Compare-Object -ReferenceObject $knownParameters -DifferenceObject $currentParams -PassThru
}
Context "Verify parameters" -Foreach @{currentParams = $currentParams } {
Context "Verify parameters" -ForEach @{currentParams = $currentParams } {
It "$commandName should contain <_> parameter" -TestCases $knownParameters {
$_ -in $currentParams | Should -Be $true
}
It "$commandName should not contain parameter: <_>" -TestCases $unknownParameters {
$_ | Should -BeNullOrEmpty
}
}
Context "Command specific details" {
It "$commandName should set OutputType to TssSession" -TestCases $commandDetails {
$_.OutputType.Name | Should -Be 'TssSession'
}
}
}

Describe "$commandName updates session object" {
Describe "$commandName works" {
BeforeAll {
$apiV = 'api/v1'
$session = New-TssSession -SecretServer $ss -Credential $ssCred
$sessionCredential = New-TssSession -SecretServer $ss -Credential $ssCred

$secretServerHost = 'https://tenant.secretservercloud.com'
$generatedAccessToken = (New-Guid).Guid
$sessionAccessToken = New-TssSession -SecretServer $secretServerHost -AccessToken $generatedAccessToken

$hostnameSampleFile = ([IO.Path]::Combine([string]$PSScriptRoot, '..\test_data', 'newsession_hostsamples.txt'))
$hostnameSamples = Get-Content $hostnameSampleFile
}
Context "Oauth2 authentication" {
It "Populates SecretServer Propety" {
$session.SecretServer | Should -Be ([uri]$ss)
Context "Validates SecretServer argument" {
It "Should catch invalid URL arguments" {
{ New-TssSession -SecretServer 'https://alpha/api/v1' -AccessToken "$(Get-Random)" } | Should -Throw
}
It "ApiVersion Propety is set" {
$session.ApiVersion | Should -Be $apiV
}
Context "Credential parameter" {
It "Populates SecretServer Property" {
$sessionCredential.SecretServer | Should -Be ([uri]$ss)
}
It "ApiUrl Propety is set" {
$session.ApiUrl | Should -Not -BeNullOrEmpty
It "ApiVersion Property is set" {
$sessionCredential.ApiVersion | Should -Be $apiV
}
It "ApiUrl Property is set" {
$sessionCredential.ApiUrl | Should -Not -BeNullOrEmpty
}
It "Populates AccessToken Property" {
$session.AccessToken | Should -Not -BeNullOrEmpty
$sessionCredential.AccessToken | Should -Not -BeNullOrEmpty
}
It "Populates RefreshToken Property" {
$session.RefreshToken | Should -Not -BeNullOrEmpty
$sessionCredential.RefreshToken | Should -Not -BeNullOrEmpty
}
It "Calculates TimeOfDeath" {
$expectedValue = [datetime]::UtcNow.Add([timespan]::FromSeconds($expiresIn))
$session.TimeOfDeath | Should -BeLessOrEqual $expectedValue
$sessionCredential.TimeOfDeath | Should -BeLessOrEqual $expectedValue
}
It "Calculates StartTime" {
$currentTime = [datetime]::UtcNow
$session.StartTime | Should -BeLessOrEqual $currentTime
$sessionCredential.StartTime | Should -BeLessOrEqual $currentTime
}
It "RefreshSession() method updates AccessToken" {
$orgAccessToken = $session.AccessToken
$session.SessionRefresh() | Should -Be $true
$session.AccessToken | Should -Not -Match $orgAccessToken
$orgAccessToken = $sessionCredential.AccessToken
$sessionCredential.SessionRefresh() | Should -Be $true
$sessionCredential.AccessToken | Should -Not -Match $orgAccessToken
}
It "SessionExpire() method should return true" {
$sessionCredential.SessionExpire() | Should -Be $true
}
}
Context "AccessToken parameter" {
It "Should set SecretServer to <_>" -TestCases $secretServerHost {
$sessionAccessToken.SecretServer | Should -Be $_
}
It "Sets AccessToken to <_>" -TestCases $sessionAccessToken {
$sessionAccessToken.AccessToken | Should $_
}
It "ApiUrl Property is set" {
$sessionAccessToken.ApiUrl | Should -Not -BeNullOrEmpty
}
It "Does not populate a RefreshToken Property" {
$sessionAccessToken.RefreshToken | Should -BeNullOrEmpty
}
It "SessionExpire() method should return true" {
$session.SessionExpire() | Should -Be $true
$sessionAccessToken.SessionExpire() | Should -Be $true
}
It "Does not calculate a TimeOfDeath" {
$expectedValue = [datetime]::UtcNow.Add([timespan]::FromSeconds($expiresIn))
$sessionAccessToken.TimeOfDeath | Should -BeLessOrEqual $expectedValue
}
It "Calculates StartTime" {
$currentTime = [datetime]::UtcNow
$sessionAccessToken.StartTime | Should -BeLessOrEqual $currentTime
}
}
}
6 changes: 5 additions & 1 deletion tests/runTests.ps1
@@ -1,4 +1,8 @@
$testRootPath = $PSScriptRoot
switch ($PSEdition) {
'Desktop' { Write-Host "Testing in Windows PowerShell" -ForegroundColor Cyan -BackgroundColor DarkBlue}
'Core' { Write-Host "Testing in Windows PowerShell" -ForegroundColor DarkBlue -BackgroundColor Cyan}
}
$testRootPath = $PSScriptRoot
$modulePsd1 = [IO.Path]::Combine(($PSScriptRoot.Trim("tests")), 'src\Thycotic.SecretServer.psd1')
$module = Import-Module $modulePsd1 -Force -PassThru

Expand Down
10 changes: 10 additions & 0 deletions tests/test_data/newsession_hostsamples.txt
@@ -0,0 +1,10 @@
https://tenant.secretservercloud.com
https://tenant.secretservercloud.com/api/v1
https://tenant.secretservercloud.com/oauth2/token
https://vault.com/SecretServer/api/v1
https://vault.com/SecretServer/oauth2/token
https://vault.com/SecretServer/SecretServer/SecretServer
https://vault.com/SecretServer/whatareyoudoing
https://vault.com/SecretServer/KarneWasHere
https://vault.com/SecretServer/KarenWasHere/api/v1
https://vault.com/SecretServer/dogdog/oauth2/api/dog/v1

0 comments on commit 9a983a3

Please sign in to comment.