2020using System . Text ;
2121using System . Security . Cryptography ;
2222using System . Text . Json ;
23+ using ResourceType = Microsoft . PowerShell . PSResourceGet . UtilClasses . ResourceType ;
2324
2425namespace Microsoft . PowerShell . PSResourceGet
2526{
@@ -46,6 +47,7 @@ internal class ContainerRegistryServerAPICalls : ServerApiCall
4647 const string containerRegistryFindImageVersionUrlTemplate = "https://{0}/v2/{1}/tags/list" ; // 0 - registry, 1 - repo(modulename)
4748 const string containerRegistryStartUploadTemplate = "https://{0}/v2/{1}/blobs/uploads/" ; // 0 - registry, 1 - packagename
4849 const string containerRegistryEndUploadTemplate = "https://{0}{1}&digest=sha256:{2}" ; // 0 - registry, 1 - location, 2 - digest
50+ const string defaultScope = "repository:*:*" ;
4951
5052 #endregion
5153
@@ -391,12 +393,18 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
391393 }
392394 else
393395 {
394- bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated ( Repository . Uri . ToString ( ) , out errRecord ) ;
396+ bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated ( Repository . Uri . ToString ( ) , out errRecord , out accessToken ) ;
395397 if ( errRecord != null )
396398 {
397399 return null ;
398400 }
399401
402+ if ( ! string . IsNullOrEmpty ( accessToken ) )
403+ {
404+ _cmdletPassedIn . WriteVerbose ( "Anonymous access token retrieved." ) ;
405+ return accessToken ;
406+ }
407+
400408 if ( ! isRepositoryUnauthenticated )
401409 {
402410 accessToken = Utils . GetAzAccessToken ( ) ;
@@ -436,15 +444,86 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
436444 /// <summary>
437445 /// Checks if container registry repository is unauthenticated.
438446 /// </summary>
439- internal bool IsContainerRegistryUnauthenticated ( string containerRegistyUrl , out ErrorRecord errRecord )
447+ internal bool IsContainerRegistryUnauthenticated ( string containerRegistyUrl , out ErrorRecord errRecord , out string anonymousAccessToken )
440448 {
441449 _cmdletPassedIn . WriteDebug ( "In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()" ) ;
442450 errRecord = null ;
451+ anonymousAccessToken = string . Empty ;
443452 string endpoint = $ "{ containerRegistyUrl } /v2/";
444453 HttpResponseMessage response ;
445454 try
446455 {
447456 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 + "&scope=" + 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 } &scope={ defaultScope } ";
490+ var results = GetHttpResponseJObjectUsingContentHeaders ( url , HttpMethod . Get , content , contentHeaders , out errRecord ) ;
491+
492+ if ( errRecord != null )
493+ {
494+ _cmdletPassedIn . WriteDebug ( $ "Failed to get access token from the realm. Error: { errRecord } ") ;
495+ return false ;
496+ }
497+
498+ if ( results == null )
499+ {
500+ _cmdletPassedIn . WriteDebug ( "Failed to get access token from the realm. results is null." ) ;
501+ return false ;
502+ }
503+
504+ if ( results [ "access_token" ] == null )
505+ {
506+ _cmdletPassedIn . WriteDebug ( $ "Failed to get access token from the realm. access_token is null. results: { results } ") ;
507+ return false ;
508+ }
509+
510+ anonymousAccessToken = results [ "access_token" ] . ToString ( ) ;
511+ _cmdletPassedIn . WriteDebug ( "Anonymous access token retrieved" ) ;
512+ return true ;
513+ }
514+ }
515+ }
516+ }
517+ }
518+ catch ( HttpRequestException hre )
519+ {
520+ errRecord = new ErrorRecord (
521+ hre ,
522+ "RegistryAnonymousAcquireError" ,
523+ ErrorCategory . ConnectionError ,
524+ this ) ;
525+
526+ return false ;
448527 }
449528 catch ( Exception e )
450529 {
0 commit comments