diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index 785c7aeae..35ff9a4be 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -47,6 +47,10 @@ internal class ContainerRegistryServerAPICalls : ServerApiCall
const string containerRegistryStartUploadTemplate = "https://{0}/v2/{1}/blobs/uploads/"; // 0 - registry, 1 - packagename
const string containerRegistryEndUploadTemplate = "https://{0}{1}&digest=sha256:{2}"; // 0 - registry, 1 - location, 2 - digest
const string defaultScope = "&scope=repository:*:*&scope=registry:catalog:*";
+ const string catalogScope = "&scope=registry:catalog:*";
+ const string grantTypeTemplate = "grant_type=access_token&service={0}{1}"; // 0 - registry, 1 - scope
+ const string authUrlTemplate = "{0}?service={1}{2}"; // 0 - realm, 1 - service, 2 - scope
+
const string containerRegistryRepositoryListTemplate = "https://{0}/v2/_catalog"; // 0 - registry
#endregion
@@ -323,7 +327,7 @@ private Stream InstallVersion(
return null;
}
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return null;
@@ -371,7 +375,7 @@ private Stream InstallVersion(
/// 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.
/// Note: Access token can be empty if the repository is unauthenticated
///
- internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
+ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()");
string accessToken = string.Empty;
@@ -393,7 +397,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
}
else
{
- bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), out errRecord, out accessToken);
+ bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), needCatalogAccess, out errRecord, out accessToken);
if (errRecord != null)
{
return null;
@@ -444,7 +448,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
///
/// Checks if container registry repository is unauthenticated.
///
- internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out ErrorRecord errRecord, out string anonymousAccessToken)
+ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, bool needCatalogAccess, out ErrorRecord errRecord, out string anonymousAccessToken)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()");
errRecord = null;
@@ -482,18 +486,22 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
return false;
}
- string content = "grant_type=access_token&service=" + service + defaultScope;
+ string content = needCatalogAccess ? String.Format(grantTypeTemplate, service, catalogScope) : String.Format(grantTypeTemplate, service, defaultScope);
+
var contentHeaders = new Collection> { new KeyValuePair("Content-Type", "application/x-www-form-urlencoded") };
- // get the anonymous access token
- var url = $"{realm}?service={service}{defaultScope}";
+ string url = needCatalogAccess ? String.Format(authUrlTemplate, realm, service, catalogScope) : String.Format(authUrlTemplate, realm, service, defaultScope);
// we dont check the errorrecord here because we want to return false if we get a 401 and not throw an error
- var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out _);
+ _cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
+ ErrorRecord errRecordTemp = null;
+
+ var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out errRecordTemp);
if (results == null)
{
_cmdletPassedIn.WriteDebug("Failed to get access token from the realm. results is null.");
+ _cmdletPassedIn.WriteDebug($"ErrorRecord: {errRecordTemp}");
return false;
}
@@ -504,6 +512,7 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
}
anonymousAccessToken = results["access_token"].ToString();
+
_cmdletPassedIn.WriteDebug("Anonymous access token retrieved");
return true;
}
@@ -984,24 +993,29 @@ internal JObject GetHttpResponseJObjectUsingContentHeaders(string url, HttpMetho
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
- if (string.IsNullOrEmpty(content))
+ // HTTP GET does not expect a body / content.
+ if (method != HttpMethod.Get)
{
- errRecord = new ErrorRecord(
- exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
- "RequestContentHeadersNullOrEmpty",
- ErrorCategory.InvalidData,
- _cmdletPassedIn);
- return null;
- }
+ if (string.IsNullOrEmpty(content))
+ {
+ errRecord = new ErrorRecord(
+ exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
+ "RequestContentHeadersNullOrEmpty",
+ ErrorCategory.InvalidData,
+ _cmdletPassedIn);
- request.Content = new StringContent(content);
- request.Content.Headers.Clear();
- if (contentHeaders != null)
- {
- foreach (var header in contentHeaders)
+ return null;
+ }
+
+ request.Content = new StringContent(content);
+ request.Content.Headers.Clear();
+ if (contentHeaders != null)
{
- request.Content.Headers.Add(header.Key, header.Value);
+ foreach (var header in contentHeaders)
+ {
+ request.Content.Headers.Add(header.Key, header.Value);
+ }
}
}
@@ -1230,7 +1244,7 @@ internal bool PushNupkgContainerRegistry(
// Get access token (includes refresh tokens)
_cmdletPassedIn.WriteVerbose($"Get access token for container registry server.");
- var containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ var containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return false;
@@ -1695,7 +1709,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
string packageNameLowercase = packageName.ToLower();
string packageNameForFind = PrependMARPrefix(packageNameLowercase);
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return emptyHashResponses;
@@ -1711,8 +1725,9 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
List allVersionsList = foundTags["tags"].ToList();
SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord);
- if (errRecord != null)
+ if (errRecord != null && sortedQualifyingPkgs?.Count == 0)
{
+ _cmdletPassedIn.WriteDebug("No qualifying packages found for the specified criteria.");
return emptyHashResponses;
}
@@ -1761,7 +1776,9 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
ErrorCategory.InvalidArgument,
this);
- return null;
+ _cmdletPassedIn.WriteError(errRecord);
+ _cmdletPassedIn.WriteDebug($"Skipping package '{packageName}' with version '{pkgVersionString}' as it is not a valid NuGet version.");
+ continue; // skip this version and continue with the next one
}
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
@@ -1804,7 +1821,7 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindPackages()");
errRecord = null;
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: true, out errRecord);
if (errRecord != null)
{
return emptyResponseResults;