Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
"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.",
"loc.input.label.forceReinstallCredentialProvider": "Reinstall the credential provider even if already installed",
"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.",
"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/",
"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",
"loc.input.label.feedUrl": "Azure Artifacts URL",
"loc.input.help.workloadIdentityServiceConnection": "If this is set, feedUrl is required. All other inputs are ignored.",
"loc.input.label.workloadIdentityServiceConnection": "'Azure DevOps' Service Connection",
"loc.messages.Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
"loc.messages.Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
"loc.messages.Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
"loc.messages.Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
"loc.messages.Info_AddingFederatedFeedAuth": "Unable to get federated credentials from service connection",
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
"loc.messages.FailedToGetServiceConnectionAuth": "Successfully added auth for feed %s."
"loc.messages.FailedToGetServiceConnectionAuth": "Successfully added auth for feed %s.",
"loc.messages.Warn_IgnoringFeedUrl": "Ignoring feedUrl"
}
62 changes: 50 additions & 12 deletions Tasks/NuGetAuthenticateV1/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,85 @@ async function main(): Promise<void> {
let forceReinstallCredentialProvider = null;
let federatedFeedAuthSuccessCount: number = 0;

var feedUrl;
var entraWifServiceConnectionName;
var serviceConnections;

try {
tl.setResourcePath(path.join(__dirname, 'task.json'));

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

serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
#if WIF
const feedUrl = tl.getInput("feedUrl");
const entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
feedUrl = tl.getInput("feedUrl", false);

// Failure case: User provides inputs for both NuGet & WIF Service Connections
if (serviceConnections.length > 0 && entraWifServiceConnectionName) {
tl.setResult(tl.TaskResult.Failed, tl.loc("Error_NuGetWithWIFNotSupported"));
return;
}

// Validate input is valid feed URL
if (feedUrl && !isValidFeed(feedUrl)) {
tl.setResult(tl.TaskResult.Failed, tl.loc("Error_InvalidFeedUrl", feedUrl));
return;
}

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

return;
}
// If the user doesn't provide a feedUrl, use the Azure Devops service connection to replace the Build Service
else if (!feedUrl && entraWifServiceConnectionName) {
} else if (entraWifServiceConnectionName && !feedUrl) {
// If the user doesn't provide a feedUrl, use the Azure Devops service connection to replace the Build Service
configureCredProviderForSameOrganizationFeeds(ProtocolType.NuGet, entraWifServiceConnectionName);

return;
}
else if (feedUrl) {
throw new Error(tl.loc("Error_MissingFeedUrlOrServiceConnection"));
} else if (feedUrl) {
// Warning case: User provides feedUrl without providing a WIF service connection
tl.warning(tl.loc("Warn_IgnoringFeedUrl"));
feedUrl = null;

// In the future, we will shift to breaking behavior
// tl.setResult(tl.TaskResult.SucceededWithIssues, tl.loc("Error_NuGetWithFeedUrlNotSupported"));
}
#endif

// Configure the credential provider for both same-organization feeds and service connections
var serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
await configureCredProvider(ProtocolType.NuGet, serviceConnections);
} catch (error) {
tl.setResult(tl.TaskResult.Failed, error);
} finally {
emitTelemetry("Packaging", "NuGetAuthenticateV1", {
'NuGetAuthenticate.ForceReinstallCredentialProvider': forceReinstallCredentialProvider,
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount,
// We have to check both input names because only WIF versions of the task are aware of aliases
"isFeedUrlIncluded": !!(tl.getInput("feedUrl") || tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
"isFeedUrlValid": isValidFeed(tl.getInput("feedUrl")) || isValidFeed(tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
"isEntraWifServiceConnectionNameIncluded": !!(tl.getInput("workloadIdentityServiceConnection")|| tl.getInput("azureDevOpsServiceConnection")),
"isServiceConnectionIncluded": !!serviceConnections.length
});
}
}

main();
/**
* Validates that the feedUrl is a valid Azure DevOps feed URL.
* Returns true if the feedUrl is valid, false otherwise.
*/
function isValidFeed(feedUrl?: string | null): boolean {
if (!feedUrl) return false;
const normalized = feedUrl.trim().replace(/^[\u2018\u2019\u201C\u201D'"]|['"\u2018\u2019\u201C\u201D]$/g, '');

const feedRegex = /^https:\/\/(?:[\w.-]+\.)?(?:dev\.azure\.com|visualstudio\.com|vsts\.me|codedev\.ms|devppe\.azure\.com|codeapp\.ms)(?:\/[^\/]+(?:\/[^\/]+)?)?\/_packaging\/[^\/]+\/nuget\/v3\/index\.json\/?$/i;

return feedRegex.test(normalized);
}

main();
10 changes: 7 additions & 3 deletions Tasks/NuGetAuthenticateV1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"version": {
"Major": 1,
"Minor": 262,
"Minor": 263,
"Patch": 0
},
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -54,9 +54,13 @@
},
"messages": {
"Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
"Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
"Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
"Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
"FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
"Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
"Warn_IgnoringFeedUrl": "Ignoring feedUrl"
}
}
8 changes: 6 additions & 2 deletions Tasks/NuGetAuthenticateV1/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"version": {
"Major": 1,
"Minor": 262,
"Minor": 263,
"Patch": 0
},
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -54,9 +54,13 @@
},
"messages": {
"Error_ServiceConnectionExists": "ms-resource:loc.messages.Error_ServiceConnectionExists",
"Error_InvalidFeedUrl": "ms-resource:loc.messages.Error_InvalidFeedUrl",
"Error_MissingFeedUrlOrServiceConnection": "ms-resource:loc.messages.Error_MissingFeedUrlOrServiceConnection",
"Error_NuGetWithFeedUrlNotSupported": "ms-resource:loc.messages.Error_NuGetWithFeedUrlNotSupported",
"Error_NuGetWithWIFNotSupported": "ms-resource:loc.messages.Error_NuGetWithWIFNotSupported",
"FailedToGetServiceConnectionAuth": "ms-resource:loc.messages.FailedToGetServiceConnectionAuth",
"Info_AddingFederatedFeedAuth": "ms-resource:loc.messages.Info_AddingFederatedFeedAuth",
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth"
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth",
"Warn_IgnoringFeedUrl": "ms-resource:loc.messages.Warn_IgnoringFeedUrl"
}
}
9 changes: 6 additions & 3 deletions Tasks/NuGetAuthenticateV1/taskJsonOverride.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
"properties": {
"EditableOptions": "False",
"MultiSelectFlatList": "False"
}
},
"visibleRule": "nuGetServiceConnections == ''"
},
{
"name": "feedUrl",
"aliases": ["azureDevOpsServiceConnectionCrossOrgFeedUrl"],
"label": "Azure Artifacts URL",
"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/",
"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",
"type": "string",
"defaultValue": "",
"required": false
"required": false,
"visibleRule": "workloadIdentityServiceConnection != ''"
},
{
"name": "forceReinstallCredentialProvider",
Expand Down
7 changes: 5 additions & 2 deletions Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
"properties": {
"EditableOptions": "False",
"MultiSelectFlatList": "False"
}
},
"visibleRule": "nuGetServiceConnections == ''"
},
{
"name": "feedUrl",
"aliases": ["azureDevOpsServiceConnectionCrossOrgFeedUrl"],
"label": "ms-resource:loc.input.label.feedUrl",
"helpMarkDown": "ms-resource:loc.input.help.feedUrl",
"type": "string",
"defaultValue": "",
"required": false
"required": false,
"visibleRule": "workloadIdentityServiceConnection != ''"
},
{
"name": "forceReinstallCredentialProvider",
Expand Down
4 changes: 2 additions & 2 deletions _generated/NuGetAuthenticateV1.versionmap.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Default|1.262.0
wif_242|1.262.1
Default|1.263.0
wif_242|1.263.1
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
"loc.input.label.forceReinstallCredentialProvider": "Reinstall the credential provider even if already installed",
"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.",
"loc.messages.Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
"loc.messages.Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
"loc.messages.Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
"loc.messages.Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
"loc.messages.Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
"loc.messages.FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
"loc.messages.Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
"loc.messages.Warn_IgnoringFeedUrl": "Ignoring feedUrl"
}
26 changes: 24 additions & 2 deletions _generated/NuGetAuthenticateV1/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,47 @@ async function main(): Promise<void> {
let forceReinstallCredentialProvider = null;
let federatedFeedAuthSuccessCount: number = 0;

var feedUrl;
var entraWifServiceConnectionName;
var serviceConnections;

try {
tl.setResourcePath(path.join(__dirname, 'task.json'));

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

serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');

// Configure the credential provider for both same-organization feeds and service connections
var serviceConnections = getPackagingServiceConnections('nuGetServiceConnections');
await configureCredProvider(ProtocolType.NuGet, serviceConnections);
} catch (error) {
tl.setResult(tl.TaskResult.Failed, error);
} finally {
emitTelemetry("Packaging", "NuGetAuthenticateV1", {
'NuGetAuthenticate.ForceReinstallCredentialProvider': forceReinstallCredentialProvider,
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount,
// We have to check both input names because only WIF versions of the task are aware of aliases
"isFeedUrlIncluded": !!(tl.getInput("feedUrl") || tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
"isFeedUrlValid": isValidFeed(tl.getInput("feedUrl")) || isValidFeed(tl.getInput("azureDevOpsServiceConnectionCrossOrgFeedUrl")),
"isEntraWifServiceConnectionNameIncluded": !!(tl.getInput("workloadIdentityServiceConnection")|| tl.getInput("azureDevOpsServiceConnection")),
"isServiceConnectionIncluded": !!serviceConnections.length
});
}
}

/**
* Validates that the feedUrl is a valid Azure DevOps feed URL.
* Returns true if the feedUrl is valid, false otherwise.
*/
function isValidFeed(feedUrl?: string | null): boolean {
if (!feedUrl) return false;
const normalized = feedUrl.trim().replace(/^[\u2018\u2019\u201C\u201D'"]|['"\u2018\u2019\u201C\u201D]$/g, '');

const feedRegex = /^https:\/\/(?:[\w.-]+\.)?(?:dev\.azure\.com|visualstudio\.com|vsts\.me|codedev\.ms|devppe\.azure\.com|codeapp\.ms)(?:\/[^\/]+(?:\/[^\/]+)?)?\/_packaging\/[^\/]+\/nuget\/v3\/index\.json\/?$/i;

return feedRegex.test(normalized);
}

main();
14 changes: 9 additions & 5 deletions _generated/NuGetAuthenticateV1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"version": {
"Major": 1,
"Minor": 262,
"Minor": 263,
"Patch": 0
},
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -54,14 +54,18 @@
},
"messages": {
"Error_ServiceConnectionExists": "An existing service connection already exists for the endpoint",
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and workloadIdentityServiceConnection must be set together.",
"Error_InvalidFeedUrl": "The provided feed URL '%s' is not a valid Azure DevOps feed URL.",
"Error_MissingFeedUrlOrServiceConnection": "Both feedUrl and azureDevOpsServiceConnection must be set together.",
"Error_NuGetWithFeedUrlNotSupported": "Cannot specify both nuGetServiceConnections and feedUrl input parameters.",
"Error_NuGetWithWIFNotSupported": "Cannot specify both nuGetServiceConnections and azureDevOpsServiceConnection(workloadIdentityServiceConnection) input parameters.",
"FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection",
"Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s."
"Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
"Warn_IgnoringFeedUrl": "Ignoring feedUrl"
},
"_buildConfigMapping": {
"Default": "1.262.0",
"Default": "1.263.0",
"LocalPackages": "1.249.4",
"wif_242": "1.262.1"
"wif_242": "1.263.1"
}
}
12 changes: 8 additions & 4 deletions _generated/NuGetAuthenticateV1/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"version": {
"Major": 1,
"Minor": 262,
"Minor": 263,
"Patch": 0
},
"minimumAgentVersion": "2.144.0",
Expand Down Expand Up @@ -54,14 +54,18 @@
},
"messages": {
"Error_ServiceConnectionExists": "ms-resource:loc.messages.Error_ServiceConnectionExists",
"Error_InvalidFeedUrl": "ms-resource:loc.messages.Error_InvalidFeedUrl",
"Error_MissingFeedUrlOrServiceConnection": "ms-resource:loc.messages.Error_MissingFeedUrlOrServiceConnection",
"Error_NuGetWithFeedUrlNotSupported": "ms-resource:loc.messages.Error_NuGetWithFeedUrlNotSupported",
"Error_NuGetWithWIFNotSupported": "ms-resource:loc.messages.Error_NuGetWithWIFNotSupported",
"FailedToGetServiceConnectionAuth": "ms-resource:loc.messages.FailedToGetServiceConnectionAuth",
"Info_AddingFederatedFeedAuth": "ms-resource:loc.messages.Info_AddingFederatedFeedAuth",
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth"
"Info_SuccessAddingFederatedFeedAuth": "ms-resource:loc.messages.Info_SuccessAddingFederatedFeedAuth",
"Warn_IgnoringFeedUrl": "ms-resource:loc.messages.Warn_IgnoringFeedUrl"
},
"_buildConfigMapping": {
"Default": "1.262.0",
"Default": "1.263.0",
"LocalPackages": "1.249.4",
"wif_242": "1.262.1"
"wif_242": "1.263.1"
}
}
Loading