@@ -8,7 +8,12 @@ import * as FakeTimers from '@sinonjs/fake-timers'
8
8
import * as sinon from 'sinon'
9
9
import { SsoAccessTokenProvider } from '../../../credentials/sso/ssoAccessTokenProvider'
10
10
import { installFakeClock } from '../../testUtil'
11
- import { getCache } from '../../../credentials/sso/cache'
11
+ import {
12
+ getCache ,
13
+ getTokenCacheFile ,
14
+ isDirSafeToDeleteFrom ,
15
+ getRegistrationCacheFile ,
16
+ } from '../../../credentials/sso/cache'
12
17
13
18
import { instance , mock , when , anything , reset } from '../../utilities/mockito'
14
19
import { makeTemporaryToolkitFolder , tryRemoveFolder } from '../../../shared/filesystemUtilities'
@@ -25,6 +30,8 @@ import { getOpenExternalStub } from '../../globalSetup.test'
25
30
import { getTestWindow } from '../../shared/vscode/window'
26
31
import { SeverityLevel } from '../../shared/vscode/message'
27
32
import { ToolkitError } from '../../../shared/errors'
33
+ import * as fs from 'fs'
34
+ import * as path from 'path'
28
35
29
36
const hourInMs = 3600000
30
37
@@ -66,6 +73,13 @@ describe('SsoAccessTokenProvider', function () {
66
73
}
67
74
}
68
75
76
+ async function makeTemporaryTokenCacheFolder ( ) {
77
+ const root = await makeTemporaryToolkitFolder ( )
78
+ const cacheDir = path . join ( root , '.aws' , 'sso' , 'cache' )
79
+ fs . mkdirSync ( cacheDir , { recursive : true } )
80
+ return cacheDir
81
+ }
82
+
69
83
before ( function ( ) {
70
84
clock = installFakeClock ( )
71
85
} )
@@ -75,7 +89,7 @@ describe('SsoAccessTokenProvider', function () {
75
89
} )
76
90
77
91
beforeEach ( async function ( ) {
78
- tempDir = await makeTemporaryToolkitFolder ( )
92
+ tempDir = await makeTemporaryTokenCacheFolder ( )
79
93
cache = getCache ( tempDir )
80
94
sut = new SsoAccessTokenProvider ( { region, startUrl } , cache , instance ( oidcClient ) )
81
95
} )
@@ -115,6 +129,74 @@ describe('SsoAccessTokenProvider', function () {
115
129
assert . strictEqual ( await cache . token . load ( startUrl ) , undefined )
116
130
} )
117
131
132
+ describe ( 'does not return old tokens' , function ( ) {
133
+ // A file is old when the creation time is under a certain date
134
+ const oldTime : Date = new Date ( 2023 , 3 , 10 ) // April 10, 2023
135
+ const nonOldTime : Date = new Date ( 2023 , 3 , 15 ) // April 15, 2023
136
+
137
+ function oldBirthtime ( file : fs . PathLike ) : fs . Stats {
138
+ return { birthtimeMs : oldTime . getTime ( ) , birthtime : oldTime } as fs . Stats
139
+ }
140
+
141
+ /** Windows edge case where birthtime does not exist, instead check ctime */
142
+ function noBirthtimeOldCTime ( file : fs . PathLike ) : fs . Stats {
143
+ return { birthtimeMs : 0 , ctime : oldTime } as fs . Stats
144
+ }
145
+
146
+ const oldStatsFuncs = [ oldBirthtime , noBirthtimeOldCTime ]
147
+
148
+ oldStatsFuncs . forEach ( invalidStatsFunc => {
149
+ it ( `deletes old invalid tokens when ${ invalidStatsFunc . name } then returns undefined` , async function ( ) {
150
+ await cache . token . save ( startUrl , { region, startUrl, token : createToken ( hourInMs ) } )
151
+ const tokenCacheFile = getTokenCacheFile ( tempDir , startUrl )
152
+ assert . strictEqual ( fs . existsSync ( tokenCacheFile ) , true )
153
+
154
+ // Set the func which returns Stats that are always 'invalid'
155
+ cache = getCache ( tempDir , invalidStatsFunc )
156
+
157
+ assert . strictEqual ( await cache . token . load ( startUrl ) , undefined )
158
+ assert . strictEqual ( fs . existsSync ( tokenCacheFile ) , false )
159
+ } )
160
+
161
+ it ( `deletes old invalid registrations when ${ invalidStatsFunc . name } then returns undefined` , async function ( ) {
162
+ const registrationKey = { region }
163
+ await cache . registration . save ( registrationKey , createRegistration ( hourInMs ) )
164
+ const registrationCacheFile = getRegistrationCacheFile ( tempDir , registrationKey )
165
+ assert . strictEqual ( fs . existsSync ( registrationCacheFile ) , true )
166
+
167
+ // Set the func which returns Stats that are always 'invalid'
168
+ cache = getCache ( tempDir , invalidStatsFunc )
169
+ assert . strictEqual ( await cache . token . load ( startUrl ) , undefined )
170
+ assert . strictEqual ( fs . existsSync ( registrationCacheFile ) , false )
171
+ } )
172
+ } )
173
+
174
+ function nonOldBirthtime ( file : fs . PathLike ) : fs . Stats {
175
+ return { birthtimeMs : nonOldTime . getTime ( ) } as fs . Stats
176
+ }
177
+
178
+ it ( `returns token from non-old file` , async function ( ) {
179
+ const token = createToken ( hourInMs )
180
+ await cache . token . save ( startUrl , { region, startUrl, token } )
181
+ const tokenCacheFile = getTokenCacheFile ( tempDir , startUrl )
182
+ assert . strictEqual ( fs . existsSync ( tokenCacheFile ) , true )
183
+
184
+ cache = getCache ( tempDir , nonOldBirthtime )
185
+
186
+ assert . deepStrictEqual ( ( await cache . token . load ( startUrl ) ) ! . token , token )
187
+ assert . strictEqual ( fs . existsSync ( tokenCacheFile ) , true )
188
+ } )
189
+
190
+ it ( 'isDirSafeToDeleteFrom()' , function ( ) {
191
+ assert . ok ( ! isDirSafeToDeleteFrom ( '.' ) )
192
+ assert . ok ( ! isDirSafeToDeleteFrom ( '/' ) )
193
+ assert . ok ( ! isDirSafeToDeleteFrom ( 'not/an/absolute/path' ) )
194
+ assert . ok ( ! isDirSafeToDeleteFrom ( '/a/b/c' ) ) // Too shallow
195
+
196
+ assert . ok ( isDirSafeToDeleteFrom ( '/a/b/c/d' ) )
197
+ } )
198
+ } )
199
+
118
200
it ( 'returns `undefined` for expired tokens that cannot be refreshed' , async function ( ) {
119
201
const expiredToken = createToken ( - hourInMs )
120
202
await cache . token . save ( startUrl , { region, startUrl, token : expiredToken } )
0 commit comments