5
5
6
6
import * as vscode from 'vscode' ;
7
7
import { v4 as uuid } from 'uuid' ;
8
+ import fetch from 'node-fetch' ;
8
9
import Keychain from './common/keychain' ;
9
10
import GitpodServer from './gitpodServer' ;
10
11
import Log from './common/logger' ;
@@ -30,6 +31,8 @@ export default class GitpodAuthenticationProvider extends Disposable implements
30
31
private _gitpodServer : GitpodServer ;
31
32
private _keychain : Keychain ;
32
33
34
+ private _serviceUrl : string ;
35
+
33
36
private _sessionsPromise : Promise < vscode . AuthenticationSession [ ] > ;
34
37
35
38
constructor ( private readonly context : vscode . ExtensionContext , logger : Log , telemetry : TelemetryReporter ) {
@@ -39,17 +42,19 @@ export default class GitpodAuthenticationProvider extends Disposable implements
39
42
this . _telemetry = telemetry ;
40
43
41
44
const gitpodHost = vscode . workspace . getConfiguration ( 'gitpod' ) . get < string > ( 'host' ) ! ;
42
- const serviceUrl = new URL ( gitpodHost ) ;
43
- this . _gitpodServer = new GitpodServer ( serviceUrl . toString ( ) , this . _logger ) ;
44
- this . _keychain = new Keychain ( this . context , `gitpod.auth.${ serviceUrl . hostname } ` , this . _logger ) ;
45
+ const gitpodHostUrl = new URL ( gitpodHost ) ;
46
+ this . _serviceUrl = gitpodHostUrl . toString ( ) . replace ( / \/ $ / , '' ) ;
47
+ this . _gitpodServer = new GitpodServer ( this . _serviceUrl , this . _logger ) ;
48
+ this . _keychain = new Keychain ( this . context , `gitpod.auth.${ gitpodHostUrl . hostname } ` , this . _logger ) ;
45
49
this . _logger . info ( `Started authentication provider for ${ gitpodHost } ` ) ;
46
50
this . _register ( vscode . workspace . onDidChangeConfiguration ( e => {
47
51
if ( e . affectsConfiguration ( 'gitpod.host' ) ) {
48
52
const gitpodHost = vscode . workspace . getConfiguration ( 'gitpod' ) . get < string > ( 'host' ) ! ;
49
- const serviceUrl = new URL ( gitpodHost ) ;
53
+ const gitpodHostUrl = new URL ( gitpodHost ) ;
54
+ this . _serviceUrl = gitpodHostUrl . toString ( ) . replace ( / \/ $ / , '' ) ;
50
55
this . _gitpodServer . dispose ( ) ;
51
- this . _gitpodServer = new GitpodServer ( serviceUrl . toString ( ) , this . _logger ) ;
52
- this . _keychain = new Keychain ( this . context , `gitpod.auth.${ serviceUrl . hostname } ` , this . _logger ) ;
56
+ this . _gitpodServer = new GitpodServer ( this . _serviceUrl , this . _logger ) ;
57
+ this . _keychain = new Keychain ( this . context , `gitpod.auth.${ gitpodHostUrl . hostname } ` , this . _logger ) ;
53
58
this . _logger . info ( `Started authentication provider for ${ gitpodHost } ` ) ;
54
59
55
60
this . checkForUpdates ( ) ;
@@ -70,16 +75,40 @@ export default class GitpodAuthenticationProvider extends Disposable implements
70
75
async getSessions ( scopes ?: string [ ] ) : Promise < vscode . AuthenticationSession [ ] > {
71
76
// For the Gitpod scope list, order doesn't matter so we immediately sort the scopes
72
77
const sortedScopes = scopes ?. sort ( ) || [ ] ;
73
- this . _logger . info ( `Getting sessions for ${ sortedScopes . length ? sortedScopes . join ( ',' ) : 'all scopes' } ...` ) ;
78
+ const validScopes = await this . fetchValidScopes ( ) ;
79
+ const sortedFilteredScopes = sortedScopes . filter ( s => ! validScopes || validScopes . includes ( s ) ) ;
80
+ this . _logger . info ( `Getting sessions for ${ sortedScopes . length ? sortedScopes . join ( ',' ) : 'all scopes' } ${ sortedScopes . length !== sortedFilteredScopes . length ? `, but valid scopes are ${ sortedFilteredScopes . join ( ',' ) } ` : '' } ...` ) ;
81
+ if ( sortedScopes . length !== sortedFilteredScopes . length ) {
82
+ this . _logger . warn ( `But valid scopes are ${ sortedFilteredScopes . join ( ',' ) } , returning session with only valid scopes...` ) ;
83
+ }
74
84
const sessions = await this . _sessionsPromise ;
75
- const finalSessions = sortedScopes . length
76
- ? sessions . filter ( session => arrayEquals ( [ ...session . scopes ] . sort ( ) , sortedScopes ) )
85
+ const finalSessions = sortedFilteredScopes . length
86
+ ? sessions . filter ( session => arrayEquals ( [ ...session . scopes ] . sort ( ) , sortedFilteredScopes ) )
77
87
: sessions ;
78
88
79
- this . _logger . info ( `Got ${ finalSessions . length } sessions for ${ sortedScopes ?. join ( ',' ) ?? 'all scopes' } ...` ) ;
89
+ this . _logger . info ( `Got ${ finalSessions . length } sessions for ${ sortedFilteredScopes ?. join ( ',' ) ?? 'all scopes' } ...` ) ;
80
90
return finalSessions ;
81
91
}
82
92
93
+ private _validScopes : string [ ] | undefined ;
94
+ private async fetchValidScopes ( ) : Promise < string [ ] | undefined > {
95
+ if ( this . _validScopes ) {
96
+ return this . _validScopes ;
97
+ }
98
+
99
+ const endpoint = `${ this . _serviceUrl } /api/oauth/inspect?client=${ vscode . env . uriScheme } -gitpod` ;
100
+ try {
101
+ const resp = await fetch ( endpoint , { timeout : 1500 } ) ;
102
+ if ( resp . ok ) {
103
+ this . _validScopes = await resp . json ( ) ;
104
+ return this . _validScopes ;
105
+ }
106
+ } catch ( e ) {
107
+ this . _logger . error ( `Error fetching endpoint ${ endpoint } ` , e ) ;
108
+ }
109
+ return undefined ;
110
+ }
111
+
83
112
private async checkForUpdates ( ) {
84
113
const previousSessions = await this . _sessionsPromise ;
85
114
this . _sessionsPromise = this . readSessions ( ) ;
@@ -187,18 +216,23 @@ export default class GitpodAuthenticationProvider extends Disposable implements
187
216
try {
188
217
// For the Gitpod scope list, order doesn't matter so we immediately sort the scopes
189
218
const sortedScopes = scopes . sort ( ) ;
219
+ const validScopes = await this . fetchValidScopes ( ) ;
220
+ const sortedFilteredScopes = sortedScopes . filter ( s => ! validScopes || validScopes . includes ( s ) ) ;
221
+ if ( sortedScopes . length !== sortedFilteredScopes . length ) {
222
+ this . _logger . warn ( `Creating session with only valid scopes ${ sortedFilteredScopes . join ( ',' ) } , original scopes were ${ sortedScopes . join ( ',' ) } ` ) ;
223
+ }
190
224
191
225
this . _telemetry . sendRawTelemetryEvent ( 'gitpod_desktop_auth' , {
192
226
kind : 'login' ,
193
- scopes : JSON . stringify ( sortedScopes ) ,
227
+ scopes : JSON . stringify ( sortedFilteredScopes ) ,
194
228
} ) ;
195
229
196
- const scopeString = sortedScopes . join ( ' ' ) ;
230
+ const scopeString = sortedFilteredScopes . join ( ' ' ) ;
197
231
const token = await this . _gitpodServer . login ( scopeString ) ;
198
- const session = await this . tokenToSession ( token , sortedScopes ) ;
232
+ const session = await this . tokenToSession ( token , sortedFilteredScopes ) ;
199
233
200
234
const sessions = await this . _sessionsPromise ;
201
- const sessionIndex = sessions . findIndex ( s => s . id === session . id || arrayEquals ( [ ...s . scopes ] . sort ( ) , sortedScopes ) ) ;
235
+ const sessionIndex = sessions . findIndex ( s => s . id === session . id || arrayEquals ( [ ...s . scopes ] . sort ( ) , sortedFilteredScopes ) ) ;
202
236
if ( sessionIndex > - 1 ) {
203
237
sessions . splice ( sessionIndex , 1 , session ) ;
204
238
} else {
0 commit comments