Skip to content

Commit 419372c

Browse files
Fix catalog access for Container Registry repositories (#1831)
1 parent c637fcf commit 419372c

File tree

2 files changed

+46
-31
lines changed

2 files changed

+46
-31
lines changed

src/code/ContainerRegistryServerAPICalls.cs

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ internal class ContainerRegistryServerAPICalls : ServerApiCall
4747
const string containerRegistryStartUploadTemplate = "https://{0}/v2/{1}/blobs/uploads/"; // 0 - registry, 1 - packagename
4848
const string containerRegistryEndUploadTemplate = "https://{0}{1}&digest=sha256:{2}"; // 0 - registry, 1 - location, 2 - digest
4949
const string defaultScope = "&scope=repository:*:*&scope=registry:catalog:*";
50+
const string catalogScope = "&scope=registry:catalog:*";
51+
const string grantTypeTemplate = "grant_type=access_token&service={0}{1}"; // 0 - registry, 1 - scope
52+
const string authUrlTemplate = "{0}?service={1}{2}"; // 0 - realm, 1 - service, 2 - scope
53+
5054
const string containerRegistryRepositoryListTemplate = "https://{0}/v2/_catalog"; // 0 - registry
5155

5256
#endregion
@@ -323,7 +327,7 @@ private Stream InstallVersion(
323327
return null;
324328
}
325329

326-
string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
330+
string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
327331
if (errRecord != null)
328332
{
329333
return null;
@@ -371,7 +375,7 @@ private Stream InstallVersion(
371375
/// If no credential provided at registration then, check if the ACR endpoint can be accessed without a token. If not, try using Azure.Identity to get the az access token, then ACR refresh token and then ACR access token.
372376
/// Note: Access token can be empty if the repository is unauthenticated
373377
/// </summary>
374-
internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
378+
internal string GetContainerRegistryAccessToken(bool needCatalogAccess, out ErrorRecord errRecord)
375379
{
376380
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()");
377381
string accessToken = string.Empty;
@@ -393,7 +397,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
393397
}
394398
else
395399
{
396-
bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), out errRecord, out accessToken);
400+
bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), needCatalogAccess, out errRecord, out accessToken);
397401
_cmdletPassedIn.WriteDebug($"Is repository unauthenticated: {isRepositoryUnauthenticated}");
398402

399403
if (errRecord != null)
@@ -446,7 +450,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
446450
/// <summary>
447451
/// Checks if container registry repository is unauthenticated.
448452
/// </summary>
449-
internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out ErrorRecord errRecord, out string anonymousAccessToken)
453+
internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, bool needCatalogAccess, out ErrorRecord errRecord, out string anonymousAccessToken)
450454
{
451455
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()");
452456
errRecord = null;
@@ -484,20 +488,24 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
484488
return false;
485489
}
486490

487-
string content = "grant_type=access_token&service=" + service + defaultScope;
491+
string content = needCatalogAccess ? String.Format(grantTypeTemplate, service, catalogScope) : String.Format(grantTypeTemplate, service, defaultScope);
492+
488493
var contentHeaders = new Collection<KeyValuePair<string, string>> { new KeyValuePair<string, string>("Content-Type", "application/x-www-form-urlencoded") };
489494

490-
// get the anonymous access token
491-
var url = $"{realm}?service={service}{defaultScope}";
495+
string url = needCatalogAccess ? String.Format(authUrlTemplate, realm, service, catalogScope) : String.Format(authUrlTemplate, realm, service, defaultScope);
492496

493497
_cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
494498

495499
// we dont check the errorrecord here because we want to return false if we get a 401 and not throw an error
496-
var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out _);
500+
_cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
501+
ErrorRecord errRecordTemp = null;
502+
503+
var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out errRecordTemp);
497504

498505
if (results == null)
499506
{
500507
_cmdletPassedIn.WriteDebug("Failed to get access token from the realm. results is null.");
508+
_cmdletPassedIn.WriteDebug($"ErrorRecord: {errRecordTemp}");
501509
return false;
502510
}
503511

@@ -508,7 +516,6 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
508516
}
509517

510518
anonymousAccessToken = results["access_token"].ToString();
511-
_cmdletPassedIn.WriteDebug("Anonymous access token retrieved");
512519
return true;
513520
}
514521
}
@@ -761,7 +768,7 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
761768
if (!NuGetVersion.TryParse(pkgVersionString, out NuGetVersion pkgVersion))
762769
{
763770
errRecord = new ErrorRecord(
764-
new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version."),
771+
new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version for package '{packageName}'."),
765772
"ParseMetadataFailure",
766773
ErrorCategory.InvalidArgument,
767774
this);
@@ -988,24 +995,29 @@ internal JObject GetHttpResponseJObjectUsingContentHeaders(string url, HttpMetho
988995
{
989996
HttpRequestMessage request = new HttpRequestMessage(method, url);
990997

991-
if (string.IsNullOrEmpty(content))
998+
// HTTP GET does not expect a body / content.
999+
if (method != HttpMethod.Get)
9921000
{
993-
errRecord = new ErrorRecord(
994-
exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
995-
"RequestContentHeadersNullOrEmpty",
996-
ErrorCategory.InvalidData,
997-
_cmdletPassedIn);
9981001

999-
return null;
1000-
}
1002+
if (string.IsNullOrEmpty(content))
1003+
{
1004+
errRecord = new ErrorRecord(
1005+
exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
1006+
"RequestContentHeadersNullOrEmpty",
1007+
ErrorCategory.InvalidData,
1008+
_cmdletPassedIn);
10011009

1002-
request.Content = new StringContent(content);
1003-
request.Content.Headers.Clear();
1004-
if (contentHeaders != null)
1005-
{
1006-
foreach (var header in contentHeaders)
1010+
return null;
1011+
}
1012+
1013+
request.Content = new StringContent(content);
1014+
request.Content.Headers.Clear();
1015+
if (contentHeaders != null)
10071016
{
1008-
request.Content.Headers.Add(header.Key, header.Value);
1017+
foreach (var header in contentHeaders)
1018+
{
1019+
request.Content.Headers.Add(header.Key, header.Value);
1020+
}
10091021
}
10101022
}
10111023

@@ -1234,7 +1246,7 @@ internal bool PushNupkgContainerRegistry(
12341246

12351247
// Get access token (includes refresh tokens)
12361248
_cmdletPassedIn.WriteVerbose($"Get access token for container registry server.");
1237-
var containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
1249+
var containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
12381250
if (errRecord != null)
12391251
{
12401252
return false;
@@ -1699,7 +1711,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
16991711
string packageNameLowercase = packageName.ToLower();
17001712

17011713
string packageNameForFind = PrependMARPrefix(packageNameLowercase);
1702-
string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
1714+
string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
17031715
if (errRecord != null)
17041716
{
17051717
return emptyHashResponses;
@@ -1715,8 +1727,9 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
17151727
List<JToken> allVersionsList = foundTags["tags"].ToList();
17161728

17171729
SortedDictionary<NuGet.Versioning.SemanticVersion, string> sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord);
1718-
if (errRecord != null)
1730+
if (errRecord != null && sortedQualifyingPkgs?.Count == 0)
17191731
{
1732+
_cmdletPassedIn.WriteDebug("No qualifying packages found for the specified criteria.");
17201733
return emptyHashResponses;
17211734
}
17221735

@@ -1760,12 +1773,14 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
17601773
if (!NuGetVersion.TryParse(pkgVersionString, out NuGetVersion pkgVersion))
17611774
{
17621775
errRecord = new ErrorRecord(
1763-
new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version."),
1776+
new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version for package '{packageName}'."),
17641777
"FindNameFailure",
17651778
ErrorCategory.InvalidArgument,
17661779
this);
17671780

1768-
return null;
1781+
_cmdletPassedIn.WriteError(errRecord);
1782+
_cmdletPassedIn.WriteDebug($"Skipping package '{packageName}' with version '{pkgVersionString}' as it is not a valid NuGet version.");
1783+
continue; // skip this version and continue with the next one
17691784
}
17701785

17711786
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
@@ -1808,7 +1823,7 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
18081823
{
18091824
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindPackages()");
18101825
errRecord = null;
1811-
string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
1826+
string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: true, out errRecord);
18121827
if (errRecord != null)
18131828
{
18141829
return emptyResponseResults;

src/code/PSRepositoryInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public enum RepositoryProviderType
104104

105105
internal bool IsMARRepository()
106106
{
107-
return (ApiVersion == APIVersion.ContainerRegistry && Uri.Host.Contains("mcr.microsoft.com"));
107+
return (ApiVersion == APIVersion.ContainerRegistry && Uri.Host.StartsWith("mcr.microsoft") );
108108
}
109109

110110
#endregion

0 commit comments

Comments
 (0)