@@ -38,14 +38,15 @@ internal class ContainerRegistryServerAPICalls : ServerApiCall
38
38
private static readonly FindResults emptyResponseResults = new FindResults ( stringResponse : Utils . EmptyStrArray , hashtableResponse : emptyHashResponses , responseType : containerRegistryFindResponseType ) ;
39
39
40
40
const string containerRegistryRefreshTokenTemplate = "grant_type=access_token&service={0}&tenant={1}&access_token={2}" ; // 0 - registry, 1 - tenant, 2 - access token
41
- const string containerRegistryAccessTokenTemplate = "grant_type=refresh_token&service={0}&scope=repository:*:*&refresh_token={1}" ; // 0 - registry, 1 - refresh token
41
+ const string containerRegistryAccessTokenTemplate = "grant_type=refresh_token&service={0}&scope=repository:*:*&scope=registry:catalog:*& refresh_token={1}" ; // 0 - registry, 1 - refresh token
42
42
const string containerRegistryOAuthExchangeUrlTemplate = "https://{0}/oauth2/exchange" ; // 0 - registry
43
43
const string containerRegistryOAuthTokenUrlTemplate = "https://{0}/oauth2/token" ; // 0 - registry
44
44
const string containerRegistryManifestUrlTemplate = "https://{0}/v2/{1}/manifests/{2}" ; // 0 - registry, 1 - repo(modulename), 2 - tag(version)
45
45
const string containerRegistryBlobDownloadUrlTemplate = "https://{0}/v2/{1}/blobs/{2}" ; // 0 - registry, 1 - repo(modulename), 2 - layer digest
46
46
const string containerRegistryFindImageVersionUrlTemplate = "https://{0}/v2/{1}/tags/list" ; // 0 - registry, 1 - repo(modulename)
47
47
const string containerRegistryStartUploadTemplate = "https://{0}/v2/{1}/blobs/uploads/" ; // 0 - registry, 1 - packagename
48
48
const string containerRegistryEndUploadTemplate = "https://{0}{1}&digest=sha256:{2}" ; // 0 - registry, 1 - location, 2 - digest
49
+ const string defaultScope = "&scope=repository:*:*&scope=registry:catalog:*" ;
49
50
const string containerRegistryRepositoryListTemplate = "https://{0}/v2/_catalog" ; // 0 - registry
50
51
51
52
#endregion
@@ -392,12 +393,18 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
392
393
}
393
394
else
394
395
{
395
- bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated ( Repository . Uri . ToString ( ) , out errRecord ) ;
396
+ bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated ( Repository . Uri . ToString ( ) , out errRecord , out accessToken ) ;
396
397
if ( errRecord != null )
397
398
{
398
399
return null ;
399
400
}
400
401
402
+ if ( ! string . IsNullOrEmpty ( accessToken ) )
403
+ {
404
+ _cmdletPassedIn . WriteVerbose ( "Anonymous access token retrieved." ) ;
405
+ return accessToken ;
406
+ }
407
+
401
408
if ( ! isRepositoryUnauthenticated )
402
409
{
403
410
accessToken = Utils . GetAzAccessToken ( ) ;
@@ -437,15 +444,82 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
437
444
/// <summary>
438
445
/// Checks if container registry repository is unauthenticated.
439
446
/// </summary>
440
- internal bool IsContainerRegistryUnauthenticated ( string containerRegistyUrl , out ErrorRecord errRecord )
447
+ internal bool IsContainerRegistryUnauthenticated ( string containerRegistyUrl , out ErrorRecord errRecord , out string anonymousAccessToken )
441
448
{
442
449
_cmdletPassedIn . WriteDebug ( "In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()" ) ;
443
450
errRecord = null ;
451
+ anonymousAccessToken = string . Empty ;
444
452
string endpoint = $ "{ containerRegistyUrl } /v2/";
445
453
HttpResponseMessage response ;
446
454
try
447
455
{
448
456
response = _sessionClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , endpoint ) ) . Result ;
457
+
458
+ if ( response . StatusCode == HttpStatusCode . Unauthorized )
459
+ {
460
+ // check if there is a auth challenge header
461
+ if ( response . Headers . WwwAuthenticate . Count ( ) > 0 )
462
+ {
463
+ var authHeader = response . Headers . WwwAuthenticate . First ( ) ;
464
+ if ( authHeader . Scheme == "Bearer" )
465
+ {
466
+ // check if there is a realm
467
+ if ( authHeader . Parameter . Contains ( "realm" ) )
468
+ {
469
+ // get the realm
470
+ var realm = authHeader . Parameter . Split ( ',' ) ? . Where ( x => x . Contains ( "realm" ) ) ? . FirstOrDefault ( ) ? . Split ( '=' ) [ 1 ] ? . Trim ( '"' ) ;
471
+ // get the service
472
+ var service = authHeader . Parameter . Split ( ',' ) ? . Where ( x => x . Contains ( "service" ) ) ? . FirstOrDefault ( ) ? . Split ( '=' ) [ 1 ] ? . Trim ( '"' ) ;
473
+
474
+ if ( string . IsNullOrEmpty ( realm ) || string . IsNullOrEmpty ( service ) )
475
+ {
476
+ errRecord = new ErrorRecord (
477
+ new InvalidOperationException ( "Failed to get realm or service from the auth challenge header." ) ,
478
+ "RegistryUnauthenticationCheckError" ,
479
+ ErrorCategory . InvalidResult ,
480
+ this ) ;
481
+
482
+ return false ;
483
+ }
484
+
485
+ string content = "grant_type=access_token&service=" + service + defaultScope ;
486
+ var contentHeaders = new Collection < KeyValuePair < string , string > > { new KeyValuePair < string , string > ( "Content-Type" , "application/x-www-form-urlencoded" ) } ;
487
+
488
+ // get the anonymous access token
489
+ var url = $ "{ realm } ?service={ service } { defaultScope } ";
490
+
491
+ // we dont check the errorrecord here because we want to return false if we get a 401 and not throw an error
492
+ var results = GetHttpResponseJObjectUsingContentHeaders ( url , HttpMethod . Get , content , contentHeaders , out _ ) ;
493
+
494
+ if ( results == null )
495
+ {
496
+ _cmdletPassedIn . WriteDebug ( "Failed to get access token from the realm. results is null." ) ;
497
+ return false ;
498
+ }
499
+
500
+ if ( results [ "access_token" ] == null )
501
+ {
502
+ _cmdletPassedIn . WriteDebug ( $ "Failed to get access token from the realm. access_token is null. results: { results } ") ;
503
+ return false ;
504
+ }
505
+
506
+ anonymousAccessToken = results [ "access_token" ] . ToString ( ) ;
507
+ _cmdletPassedIn . WriteDebug ( "Anonymous access token retrieved" ) ;
508
+ return true ;
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+ catch ( HttpRequestException hre )
515
+ {
516
+ errRecord = new ErrorRecord (
517
+ hre ,
518
+ "RegistryAnonymousAcquireError" ,
519
+ ErrorCategory . ConnectionError ,
520
+ this ) ;
521
+
522
+ return false ;
449
523
}
450
524
catch ( Exception e )
451
525
{
@@ -1756,7 +1830,7 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
1756
1830
}
1757
1831
1758
1832
// This remove the 'psresource/' prefix from the repository name for comparison with wildcard.
1759
- string moduleName = repositoryName . Substring ( 11 ) ;
1833
+ string moduleName = repositoryName . StartsWith ( "psresource/" ) ? repositoryName . Substring ( 11 ) : repositoryName ;
1760
1834
1761
1835
WildcardPattern wildcardPattern = new WildcardPattern ( packageName , WildcardOptions . IgnoreCase ) ;
1762
1836
0 commit comments