Skip to content

Commit d71f376

Browse files
authored
NuGet Authentication Bugfix: Stop Failing when FeedUrl is Provided (#21288)
* Add validation at execution start * Streamline failure cases * Fail fast & display warning before configuring cred provider * Add alias, tweak visibility conditions, feed validation * Telemetry fix in non-wif, Update Input Var name, help prompt fix * rebuild
1 parent 7cee1b9 commit d71f376

File tree

19 files changed

+240
-78
lines changed

19 files changed

+240
-78
lines changed

Tasks/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
"loc.input.help.nuGetServiceConnections": "Comma-separated list of NuGet service connection names for feeds outside this organization/collection. For feeds in this organization/collection, leave this blank; the build’s credentials are used automatically.",
88
"loc.input.label.forceReinstallCredentialProvider": "Reinstall the credential provider even if already installed",
99
"loc.input.help.forceReinstallCredentialProvider": "If the credential provider is already installed in the user profile, determines if it is overwritten with the task-provided credential provider. This may upgrade (or potentially downgrade) the credential provider.",
10-
"loc.input.help.feedUrl": "If this is set, workloadIdentityServiceConnection is required. All other inputs are ignored. Not compatible with nuGetServiceConnections. Feed Url should be in the NuGet service index format, e.g. https://pkgs.dev.azure.com/{ORG_NAME}/{PROJECT}/_packaging/{FEED_NAME}/nuget/v3/index.json/",
10+
"loc.input.help.feedUrl": "If this is set, azureDevOpsServiceConnection is required. All other inputs are ignored. Not compatible with nuGetServiceConnections. Feed Url should be in the NuGet service index format, e.g. https://pkgs.dev.azure.com/{ORG_NAME}/{PROJECT}/_packaging/{FEED_NAME}/nuget/v3/index.json",
1111
"loc.input.label.feedUrl": "Azure Artifacts URL",
1212
"loc.input.help.workloadIdentityServiceConnection": "If this is set, feedUrl is required. All other inputs are ignored.",
1313
"loc.input.label.workloadIdentityServiceConnection": "'Azure DevOps' Service Connection",
1414
"loc.messages.Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
15-
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
15+
"loc.messages.Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
16+
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
17+
"loc.messages.Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
18+
"loc.messages.Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
1619
"loc.messages.Info_AddingFederatedFeedAuth": "Unable to get federated credentials from service connection",
1720
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
18-
"loc.messages.FailedToGetServiceConnectionAuth": "Successfully added auth for feed %s."
21+
"loc.messages.FailedToGetServiceConnectionAuth": "Successfully added auth for feed %s.",
22+
"loc.messages.Warn_IgnoringFeedUrl": "Ignoring feedUrl"
1923
}

Tasks/NuGetAuthenticateV1/main.ts

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,85 @@ async function main(): Promise<void> {
1212
let forceReinstallCredentialProvider = null;
1313
let federatedFeedAuthSuccessCount: number = 0;
1414

15+
var feedUrl;
16+
var entraWifServiceConnectionName;
17+
var serviceConnections;
18+
1519
try {
1620
tl.setResourcePath(path.join(__dirname, 'task.json'));
1721

1822
// Install the credential provider
1923
forceReinstallCredentialProvider = tl.getBoolInput("forceReinstallCredentialProvider", false);
2024
await installCredProviderToUserProfile(forceReinstallCredentialProvider);
2125

26+
serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
2227
#if WIF
23-
const feedUrl = tl.getInput("feedUrl");
24-
const entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
28+
entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
29+
feedUrl = tl.getInput("feedUrl", false);
30+
31+
// Failure case: User provides inputs for both NuGet & WIF Service Connections
32+
if (serviceConnections.length > 0 && entraWifServiceConnectionName) {
33+
tl.setResult(tl.TaskResult.Failed, tl.loc("Error_NuGetWithWIFNotSupported"));
34+
return;
35+
}
36+
37+
// Validate input is valid feed URL
38+
if (feedUrl && !isValidFeed(feedUrl)) {
39+
tl.setResult(tl.TaskResult.Failed, tl.loc("Error_InvalidFeedUrl", feedUrl));
40+
return;
41+
}
2542

2643
// Only cross-org feedUrls are supported with Azure Devops service connections. If feedUrl is internal, the task will fail.
27-
if (feedUrl && entraWifServiceConnectionName) {
44+
if (entraWifServiceConnectionName && feedUrl ) {
2845
tl.debug(tl.loc("Info_AddingFederatedFeedAuth", entraWifServiceConnectionName, feedUrl));
2946
await configureEntraCredProvider(ProtocolType.NuGet, entraWifServiceConnectionName, feedUrl);
3047
federatedFeedAuthSuccessCount++;
3148
console.log(tl.loc("Info_SuccessAddingFederatedFeedAuth", feedUrl));
3249

3350
return;
34-
}
35-
// If the user doesn't provide a feedUrl, use the Azure Devops service connection to replace the Build Service
36-
else if (!feedUrl && entraWifServiceConnectionName) {
51+
} else if (entraWifServiceConnectionName && !feedUrl) {
52+
// If the user doesn't provide a feedUrl, use the Azure Devops service connection to replace the Build Service
3753
configureCredProviderForSameOrganizationFeeds(ProtocolType.NuGet, entraWifServiceConnectionName);
54+
3855
return;
39-
}
40-
else if (feedUrl) {
41-
throw new Error(tl.loc("Error_MissingFeedUrlOrServiceConnection"));
56+
} else if (feedUrl) {
57+
// Warning case: User provides feedUrl without providing a WIF service connection
58+
tl.warning(tl.loc("Warn_IgnoringFeedUrl"));
59+
feedUrl = null;
60+
61+
// In the future, we will shift to breaking behavior
62+
// tl.setResult(tl.TaskResult.SucceededWithIssues, tl.loc("Error_NuGetWithFeedUrlNotSupported"));
4263
}
4364
#endif
4465

4566
// Configure the credential provider for both same-organization feeds and service connections
46-
var serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
4767
await configureCredProvider(ProtocolType.NuGet, serviceConnections);
4868
} catch (error) {
4969
tl.setResult(tl.TaskResult.Failed, error);
5070
} finally {
5171
emitTelemetry("Packaging", "NuGetAuthenticateV1", {
5272
'NuGetAuthenticate.ForceReinstallCredentialProvider': forceReinstallCredentialProvider,
53-
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount
73+
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount,
74+
// We have to check both input names because only WIF versions of the task are aware of aliases
75+
"isFeedUrlIncluded": !!(tl.getInput("feedUrl") || tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
76+
"isFeedUrlValid": isValidFeed(tl.getInput("feedUrl")) || isValidFeed(tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
77+
"isEntraWifServiceConnectionNameIncluded": !!(tl.getInput("workloadIdentityServiceConnection")|| tl.getInput("azureDevOpsServiceConnection")),
78+
"isServiceConnectionIncluded": !!serviceConnections.length
5479
});
5580
}
5681
}
5782

58-
main();
83+
/**
84+
* Validates that the feedUrl is a valid Azure DevOps feed URL.
85+
* Returns true if the feedUrl is valid, false otherwise.
86+
*/
87+
function isValidFeed(feedUrl?: string | null): boolean {
88+
if (!feedUrl) return false;
89+
const normalized = feedUrl.trim().replace(/^[\u2018\u2019\u201C\u201D'"]|['"\u2018\u2019\u201C\u201D]$/g, '');
90+
91+
const feedRegex = /^https:\/\/(?:[\w.-]+\.)?(?:dev\.azure\.com|visualstudio\.com|vsts\.me|codedev\.ms|devppe\.azure\.com|codeapp\.ms)(?:\/[^\/]+(?:\/[^\/]+)?)?\/_packaging\/[^\/]+\/nuget\/v3\/index\.json\/?$/i;
92+
93+
return feedRegex.test(normalized);
94+
}
95+
96+
main();

Tasks/NuGetAuthenticateV1/task.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"version": {
1515
"Major": 1,
16-
"Minor": 262,
16+
"Minor": 263,
1717
"Patch": 0
1818
},
1919
"minimumAgentVersion": "2.144.0",
@@ -54,9 +54,13 @@
5454
},
5555
"messages": {
5656
"Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
57-
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
57+
"Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
58+
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
59+
"Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
60+
"Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
5861
"FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
5962
"Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
60-
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
63+
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
64+
"Warn_IgnoringFeedUrl": "Ignoring feedUrl"
6165
}
6266
}

Tasks/NuGetAuthenticateV1/task.loc.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"version": {
1515
"Major": 1,
16-
"Minor": 262,
16+
"Minor": 263,
1717
"Patch": 0
1818
},
1919
"minimumAgentVersion": "2.144.0",
@@ -54,9 +54,13 @@
5454
},
5555
"messages": {
5656
"Error_ServiceConnectionExists": "ms-resource:loc.messages.Error_ServiceConnectionExists",
57+
"Error_InvalidFeedUrl": "ms-resource:loc.messages.Error_InvalidFeedUrl",
5758
"Error_MissingFeedUrlOrServiceConnection": "ms-resource:loc.messages.Error_MissingFeedUrlOrServiceConnection",
59+
"Error_NuGetWithFeedUrlNotSupported": "ms-resource:loc.messages.Error_NuGetWithFeedUrlNotSupported",
60+
"Error_NuGetWithWIFNotSupported": "ms-resource:loc.messages.Error_NuGetWithWIFNotSupported",
5861
"FailedToGetServiceConnectionAuth": "ms-resource:loc.messages.FailedToGetServiceConnectionAuth",
5962
"Info_AddingFederatedFeedAuth": "ms-resource:loc.messages.Info_AddingFederatedFeedAuth",
60-
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth"
63+
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth",
64+
"Warn_IgnoringFeedUrl": "ms-resource:loc.messages.Warn_IgnoringFeedUrl"
6165
}
6266
}

Tasks/NuGetAuthenticateV1/taskJsonOverride.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
"properties": {
1111
"EditableOptions": "False",
1212
"MultiSelectFlatList": "False"
13-
}
13+
},
14+
"visibleRule": "nuGetServiceConnections == ''"
1415
},
1516
{
1617
"name": "feedUrl",
18+
"aliases": ["azureDevOpsServiceConnectionCrossOrgFeedUrl"],
1719
"label": "Azure Artifacts URL",
18-
"helpMarkDown": "If this is set, workloadIdentityServiceConnection is required. All other inputs are ignored. Not compatible with nuGetServiceConnections. Feed Url should be in the NuGet service index format, e.g. https://pkgs.dev.azure.com/{ORG_NAME}/{PROJECT}/_packaging/{FEED_NAME}/nuget/v3/index.json/",
20+
"helpMarkDown": "If this is set, azureDevOpsServiceConnection is required. All other inputs are ignored. Not compatible with nuGetServiceConnections. Feed Url should be in the NuGet service index format, e.g. https://pkgs.dev.azure.com/{ORG_NAME}/{PROJECT}/_packaging/{FEED_NAME}/nuget/v3/index.json",
1921
"type": "string",
2022
"defaultValue": "",
21-
"required": false
23+
"required": false,
24+
"visibleRule": "workloadIdentityServiceConnection != ''"
2225
},
2326
{
2427
"name": "forceReinstallCredentialProvider",

Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
"properties": {
1111
"EditableOptions": "False",
1212
"MultiSelectFlatList": "False"
13-
}
13+
},
14+
"visibleRule": "nuGetServiceConnections == ''"
1415
},
1516
{
1617
"name": "feedUrl",
18+
"aliases": ["azureDevOpsServiceConnectionCrossOrgFeedUrl"],
1719
"label": "ms-resource:loc.input.label.feedUrl",
1820
"helpMarkDown": "ms-resource:loc.input.help.feedUrl",
1921
"type": "string",
2022
"defaultValue": "",
21-
"required": false
23+
"required": false,
24+
"visibleRule": "workloadIdentityServiceConnection != ''"
2225
},
2326
{
2427
"name": "forceReinstallCredentialProvider",
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Default|1.262.0
2-
wif_242|1.262.1
1+
Default|1.263.0
2+
wif_242|1.263.1

_generated/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
"loc.input.label.forceReinstallCredentialProvider": "Reinstall the credential provider even if already installed",
99
"loc.input.help.forceReinstallCredentialProvider": "If the credential provider is already installed in the user profile, determines if it is overwritten with the task-provided credential provider. This may upgrade (or potentially downgrade) the credential provider.",
1010
"loc.messages.Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
11-
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
11+
"loc.messages.Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
12+
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
13+
"loc.messages.Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
14+
"loc.messages.Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
1215
"loc.messages.FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
1316
"loc.messages.Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
14-
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
17+
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
18+
"loc.messages.Warn_IgnoringFeedUrl": "Ignoring feedUrl"
1519
}

_generated/NuGetAuthenticateV1/main.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,47 @@ async function main(): Promise<void> {
99
let forceReinstallCredentialProvider = null;
1010
let federatedFeedAuthSuccessCount: number = 0;
1111

12+
var feedUrl;
13+
var entraWifServiceConnectionName;
14+
var serviceConnections;
15+
1216
try {
1317
tl.setResourcePath(path.join(__dirname, 'task.json'));
1418

1519
// Install the credential provider
1620
forceReinstallCredentialProvider = tl.getBoolInput("forceReinstallCredentialProvider", false);
1721
await installCredProviderToUserProfile(forceReinstallCredentialProvider);
1822

23+
serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
1924

2025
// Configure the credential provider for both same-organization feeds and service connections
21-
var serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
2226
await configureCredProvider(ProtocolType.NuGet, serviceConnections);
2327
} catch (error) {
2428
tl.setResult(tl.TaskResult.Failed, error);
2529
} finally {
2630
emitTelemetry("Packaging", "NuGetAuthenticateV1", {
2731
'NuGetAuthenticate.ForceReinstallCredentialProvider': forceReinstallCredentialProvider,
28-
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount
32+
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount,
33+
// We have to check both input names because only WIF versions of the task are aware of aliases
34+
"isFeedUrlIncluded": !!(tl.getInput("feedUrl") || tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
35+
"isFeedUrlValid": isValidFeed(tl.getInput("feedUrl")) || isValidFeed(tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
36+
"isEntraWifServiceConnectionNameIncluded": !!(tl.getInput("workloadIdentityServiceConnection")|| tl.getInput("azureDevOpsServiceConnection")),
37+
"isServiceConnectionIncluded": !!serviceConnections.length
2938
});
3039
}
3140
}
3241

42+
/**
43+
* Validates that the feedUrl is a valid Azure DevOps feed URL.
44+
* Returns true if the feedUrl is valid, false otherwise.
45+
*/
46+
function isValidFeed(feedUrl?: string | null): boolean {
47+
if (!feedUrl) return false;
48+
const normalized = feedUrl.trim().replace(/^[\u2018\u2019\u201C\u201D'"]|['"\u2018\u2019\u201C\u201D]$/g, '');
49+
50+
const feedRegex = /^https:\/\/(?:[\w.-]+\.)?(?:dev\.azure\.com|visualstudio\.com|vsts\.me|codedev\.ms|devppe\.azure\.com|codeapp\.ms)(?:\/[^\/]+(?:\/[^\/]+)?)?\/_packaging\/[^\/]+\/nuget\/v3\/index\.json\/?$/i;
51+
52+
return feedRegex.test(normalized);
53+
}
54+
3355
main();

_generated/NuGetAuthenticateV1/task.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"version": {
1515
"Major": 1,
16-
"Minor": 262,
16+
"Minor": 263,
1717
"Patch": 0
1818
},
1919
"minimumAgentVersion": "2.144.0",
@@ -54,14 +54,18 @@
5454
},
5555
"messages": {
5656
"Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
57-
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
57+
"Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
58+
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
59+
"Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
60+
"Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
5861
"FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
5962
"Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
60-
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
63+
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
64+
"Warn_IgnoringFeedUrl": "Ignoring feedUrl"
6165
},
6266
"_buildConfigMapping": {
63-
"Default": "1.262.0",
67+
"Default": "1.263.0",
6468
"LocalPackages": "1.249.4",
65-
"wif_242": "1.262.1"
69+
"wif_242": "1.263.1"
6670
}
6771
}

0 commit comments

Comments
 (0)