6
6
import * as vscode from 'vscode' ;
7
7
import * as azdata from 'azdata' ;
8
8
import * as nls from 'vscode-nls' ;
9
+ import * as tunnel from "tunnel" ;
10
+ import * as http from "http" ;
11
+ import * as https from "https" ;
9
12
10
13
import {
11
14
AzureAccount ,
@@ -26,13 +29,19 @@ import { errorToPromptFailedResult } from './networkUtils';
26
29
import { MsalCachePluginProvider } from '../utils/msalCachePlugin' ;
27
30
import { isErrorResponseBodyWithError } from '../../azureResource/utils' ;
28
31
import axios , { AxiosResponse , AxiosRequestConfig } from 'axios' ;
32
+ import { getProxyAgentOptions } from '../../proxy' ;
29
33
const localize = nls . loadMessageBundle ( ) ;
30
34
31
35
export type GetTenantsResponseData = {
32
36
value : TenantResponse [ ] ;
33
37
error ?: string ;
34
38
}
35
39
40
+ interface ProxyAgent {
41
+ isHttps : boolean ;
42
+ agent : http . Agent | https . Agent ;
43
+ }
44
+
36
45
export abstract class AzureAuth implements vscode . Disposable {
37
46
protected readonly memdb = new MemoryDatabase < string > ( ) ;
38
47
protected readonly loginEndpointUrl : string ;
@@ -442,20 +451,138 @@ export abstract class AzureAuth implements vscode.Disposable {
442
451
443
452
//#region network functions
444
453
445
- private async makeGetRequest < T > ( url : string , token : string ) : Promise < AxiosResponse < T > > {
454
+ private async makeGetRequest < T > ( requestUrl : string , token : string ) : Promise < AxiosResponse < T > > {
446
455
const config : AxiosRequestConfig = {
447
456
headers : {
448
457
'Content-Type' : 'application/json' ,
449
- 'Authorization' : `Bearer ${ token } `
458
+ 'Authorization' : `Bearer ${ token } ` ,
450
459
} ,
451
460
validateStatus : ( ) => true // Never throw
452
461
} ;
453
462
454
- const response : AxiosResponse = await axios . get < T > ( url , config ) ;
455
- Logger . piiSanitized ( 'GET request ' , [ { name : 'response' , objOrArray : response . data ?. value as TenantResponse [ ] ?? response . data as GetTenantsResponseData } ] , [ ] , url , ) ;
463
+ const httpConfig = vscode . workspace . getConfiguration ( "http" ) ;
464
+ let proxy : string | undefined = httpConfig [ 'proxy' ] as string ;
465
+ if ( ! proxy ) {
466
+ Logger . verbose ( "Workspace HTTP config didn't contain a proxy endpoint. Checking environment variables." ) ;
467
+
468
+ proxy = this . loadEnvironmentProxyValue ( ) ;
469
+ }
470
+
471
+ if ( proxy ) {
472
+ Logger . verbose ( "Proxy endpoint found in environment variables or workspace configuration." ) ;
473
+
474
+ // Turning off automatic proxy detection to avoid issues with tunneling agent by setting proxy to false.
475
+ // https://github.com/axios/axios/blob/bad6d8b97b52c0c15311c92dd596fc0bff122651/lib/adapters/http.js#L85
476
+ config . proxy = false ;
477
+
478
+ const agent = this . createProxyAgent ( requestUrl , proxy , httpConfig [ "proxyStrictSSL" ] ) ;
479
+ if ( agent . isHttps ) {
480
+ config . httpsAgent = agent . agent ;
481
+ }
482
+ else {
483
+ config . httpAgent = agent . agent ;
484
+ }
485
+
486
+ const HTTPS_PORT = 443 ;
487
+ const HTTP_PORT = 80 ;
488
+ const parsedRequestUrl = url . parse ( requestUrl ) ;
489
+ const port = parsedRequestUrl . protocol ?. startsWith ( "https" ) ? HTTPS_PORT : HTTP_PORT ;
490
+
491
+ // Request URL will include HTTPS port 443 ('https://management.azure.com:443/tenants?api-version=2019-11-01'), so
492
+ // that Axios doesn't try to reach this URL with HTTP port 80 on HTTP proxies, which result in an error. See https://github.com/axios/axios/issues/925
493
+ const requestUrlWithPort = `${ parsedRequestUrl . protocol } //${ parsedRequestUrl . hostname } :${ port } ${ parsedRequestUrl . path } ` ;
494
+ const response : AxiosResponse = await axios . get < T > ( requestUrlWithPort , config ) ;
495
+ Logger . piiSanitized ( 'GET request ' , [ { name : 'response' , objOrArray : response . data ?. value as TenantResponse [ ] ?? response . data as GetTenantsResponseData } ] , [ ] , requestUrl , ) ;
496
+ return response ;
497
+ }
498
+
499
+ Logger . verbose ( "No proxy endpoint was found in the HTTP_PROXY, HTTPS_PROXY environment variables or in the workspace HTTP configuration." ) ;
500
+ const response : AxiosResponse = await axios . get < T > ( requestUrl , config ) ;
501
+ Logger . piiSanitized ( 'GET request ' , [ { name : 'response' , objOrArray : response . data ?. value as TenantResponse [ ] ?? response . data as GetTenantsResponseData } ] , [ ] , requestUrl , ) ;
456
502
return response ;
457
503
}
458
504
505
+ private loadEnvironmentProxyValue ( ) : string | undefined {
506
+ const HTTP_PROXY = "HTTP_PROXY" ;
507
+ const HTTPS_PROXY = "HTTPS_PROXY" ;
508
+
509
+ if ( ! process ) {
510
+ Logger . verbose ( "No process object found, unable to read environment variables for proxy." ) ;
511
+ return undefined ;
512
+ }
513
+
514
+ if ( process . env [ HTTP_PROXY ] || process . env [ HTTP_PROXY . toLowerCase ( ) ] ) {
515
+ Logger . verbose ( "Loading proxy value from HTTP_PROXY environment variable." ) ;
516
+
517
+ return process . env [ HTTP_PROXY ] || process . env [ HTTP_PROXY . toLowerCase ( ) ] ;
518
+ }
519
+ else if ( process . env [ HTTPS_PROXY ] || process . env [ HTTPS_PROXY . toLowerCase ( ) ] ) {
520
+ Logger . verbose ( "Loading proxy value from HTTPS_PROXY environment variable." ) ;
521
+
522
+ return process . env [ HTTPS_PROXY ] || process . env [ HTTPS_PROXY . toLowerCase ( ) ] ;
523
+ }
524
+
525
+ Logger . verbose ( "No proxy value found in either HTTPS_PROXY or HTTP_PROXY environment variables." ) ;
526
+
527
+ return undefined ;
528
+ }
529
+
530
+ private createProxyAgent ( requestUrl : string , proxy : string , proxyStrictSSL : boolean ) : ProxyAgent {
531
+ const agentOptions = getProxyAgentOptions ( url . parse ( requestUrl ) , proxy , proxyStrictSSL ) ;
532
+ if ( ! agentOptions || ! agentOptions . host || ! agentOptions . port ) {
533
+ Logger . verbose ( "Unable to read proxy agent options to create proxy agent." ) ;
534
+ throw new Error ( "Unable to read proxy agent options to create proxy agent." ) ;
535
+ }
536
+
537
+ let tunnelOptions : tunnel . HttpsOverHttpsOptions = { } ;
538
+ if ( typeof agentOptions . auth === 'string' && agentOptions . auth ) {
539
+ tunnelOptions = {
540
+ proxy : {
541
+ proxyAuth : agentOptions . auth ,
542
+ host : agentOptions . host ,
543
+ port : Number ( agentOptions . port ) ,
544
+ }
545
+ } ;
546
+ }
547
+ else {
548
+ tunnelOptions = {
549
+ proxy : {
550
+ host : agentOptions . host ,
551
+ port : Number ( agentOptions . port ) ,
552
+
553
+ }
554
+ } ;
555
+ }
556
+
557
+ const isHttpsRequest = requestUrl . startsWith ( "https" ) ;
558
+ const isHttpsProxy = proxy . startsWith ( "https" ) ;
559
+ const proxyAgent = {
560
+ isHttps : isHttpsProxy ,
561
+ agent : this . createTunnelingAgent ( isHttpsRequest , isHttpsProxy , tunnelOptions ) ,
562
+ } as ProxyAgent ;
563
+
564
+ return proxyAgent ;
565
+ }
566
+
567
+ private createTunnelingAgent ( isHttpsRequest : boolean , isHttpsProxy : boolean , tunnelOptions : tunnel . HttpsOverHttpsOptions ) : http . Agent | https . Agent {
568
+ if ( isHttpsRequest && isHttpsProxy ) {
569
+ Logger . verbose ( "Creating https request over https proxy tunneling agent" ) ;
570
+ return tunnel . httpsOverHttps ( tunnelOptions ) ;
571
+ }
572
+ else if ( isHttpsRequest && ! isHttpsProxy ) {
573
+ Logger . verbose ( "Creating https request over http proxy tunneling agent" ) ;
574
+ return tunnel . httpsOverHttp ( tunnelOptions ) ;
575
+ }
576
+ else if ( ! isHttpsRequest && isHttpsProxy ) {
577
+ Logger . verbose ( "Creating http request over https proxy tunneling agent" ) ;
578
+ return tunnel . httpOverHttps ( tunnelOptions ) ;
579
+ }
580
+ else {
581
+ Logger . verbose ( "Creating http request over http proxy tunneling agent" ) ;
582
+ return tunnel . httpOverHttp ( tunnelOptions ) ;
583
+ }
584
+ }
585
+
459
586
//#endregion
460
587
461
588
//#region inconsequential
0 commit comments