diff --git a/Tasks/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson b/Tasks/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson index 2e81c04e9daf..3d8063587869 100644 --- a/Tasks/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson @@ -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" } \ No newline at end of file diff --git a/Tasks/NuGetAuthenticateV1/main.ts b/Tasks/NuGetAuthenticateV1/main.ts index df5e32fdb5af..8620c11eb6ff 100644 --- a/Tasks/NuGetAuthenticateV1/main.ts +++ b/Tasks/NuGetAuthenticateV1/main.ts @@ -12,6 +12,10 @@ async function main(): Promise { let forceReinstallCredentialProvider = null; let federatedFeedAuthSuccessCount: number = 0; + var feedUrl; + var entraWifServiceConnectionName; + var serviceConnections; + try { tl.setResourcePath(path.join(__dirname, 'task.json')); @@ -19,40 +23,74 @@ async function main(): Promise { 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(); \ No newline at end of file diff --git a/Tasks/NuGetAuthenticateV1/task.json b/Tasks/NuGetAuthenticateV1/task.json index 2baa08123837..15096e661e71 100644 --- a/Tasks/NuGetAuthenticateV1/task.json +++ b/Tasks/NuGetAuthenticateV1/task.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 0 }, "minimumAgentVersion": "2.144.0", @@ -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" } } \ No newline at end of file diff --git a/Tasks/NuGetAuthenticateV1/task.loc.json b/Tasks/NuGetAuthenticateV1/task.loc.json index b03b80357030..caeb8e6cb6dd 100644 --- a/Tasks/NuGetAuthenticateV1/task.loc.json +++ b/Tasks/NuGetAuthenticateV1/task.loc.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 0 }, "minimumAgentVersion": "2.144.0", @@ -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" } } \ No newline at end of file diff --git a/Tasks/NuGetAuthenticateV1/taskJsonOverride.json b/Tasks/NuGetAuthenticateV1/taskJsonOverride.json index bb0bb163e775..234325ff52da 100644 --- a/Tasks/NuGetAuthenticateV1/taskJsonOverride.json +++ b/Tasks/NuGetAuthenticateV1/taskJsonOverride.json @@ -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", diff --git a/Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json b/Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json index 8667786cb42c..11c5a07b05b5 100644 --- a/Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json +++ b/Tasks/NuGetAuthenticateV1/taskJsonOverride.loc.json @@ -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", diff --git a/_generated/NuGetAuthenticateV1.versionmap.txt b/_generated/NuGetAuthenticateV1.versionmap.txt index 14c251cb566d..209d7a74fc76 100644 --- a/_generated/NuGetAuthenticateV1.versionmap.txt +++ b/_generated/NuGetAuthenticateV1.versionmap.txt @@ -1,2 +1,2 @@ -Default|1.262.0 -wif_242|1.262.1 +Default|1.263.0 +wif_242|1.263.1 diff --git a/_generated/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson b/_generated/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson index b772db129a3a..d21177e3f8dc 100644 --- a/_generated/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson +++ b/_generated/NuGetAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson @@ -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" } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1/main.ts b/_generated/NuGetAuthenticateV1/main.ts index 690ab25307ce..2cf33d7c3dd4 100644 --- a/_generated/NuGetAuthenticateV1/main.ts +++ b/_generated/NuGetAuthenticateV1/main.ts @@ -9,6 +9,10 @@ async function main(): Promise { let forceReinstallCredentialProvider = null; let federatedFeedAuthSuccessCount: number = 0; + var feedUrl; + var entraWifServiceConnectionName; + var serviceConnections; + try { tl.setResourcePath(path.join(__dirname, 'task.json')); @@ -16,18 +20,36 @@ async function main(): Promise { 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(); diff --git a/_generated/NuGetAuthenticateV1/task.json b/_generated/NuGetAuthenticateV1/task.json index a07f1ba5be30..ce0405430d17 100644 --- a/_generated/NuGetAuthenticateV1/task.json +++ b/_generated/NuGetAuthenticateV1/task.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 0 }, "minimumAgentVersion": "2.144.0", @@ -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" } } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1/task.loc.json b/_generated/NuGetAuthenticateV1/task.loc.json index a313d57f7bff..29d1acbf3242 100644 --- a/_generated/NuGetAuthenticateV1/task.loc.json +++ b/_generated/NuGetAuthenticateV1/task.loc.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 0 }, "minimumAgentVersion": "2.144.0", @@ -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" } } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1/taskJsonOverride.json b/_generated/NuGetAuthenticateV1/taskJsonOverride.json index bb0bb163e775..234325ff52da 100644 --- a/_generated/NuGetAuthenticateV1/taskJsonOverride.json +++ b/_generated/NuGetAuthenticateV1/taskJsonOverride.json @@ -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", diff --git a/_generated/NuGetAuthenticateV1/taskJsonOverride.loc.json b/_generated/NuGetAuthenticateV1/taskJsonOverride.loc.json index 8667786cb42c..11c5a07b05b5 100644 --- a/_generated/NuGetAuthenticateV1/taskJsonOverride.loc.json +++ b/_generated/NuGetAuthenticateV1/taskJsonOverride.loc.json @@ -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", diff --git a/_generated/NuGetAuthenticateV1_Wif/Strings/resources.resjson/en-US/resources.resjson b/_generated/NuGetAuthenticateV1_Wif/Strings/resources.resjson/en-US/resources.resjson index 8cd3d269e089..a0c75befbf46 100644 --- a/_generated/NuGetAuthenticateV1_Wif/Strings/resources.resjson/en-US/resources.resjson +++ b/_generated/NuGetAuthenticateV1_Wif/Strings/resources.resjson/en-US/resources.resjson @@ -6,14 +6,18 @@ "loc.input.label.workloadIdentityServiceConnection": "'Azure DevOps' Service Connection", "loc.input.help.workloadIdentityServiceConnection": "If this is set, feedUrl is required. All other inputs are ignored.", "loc.input.label.feedUrl": "Azure Artifacts URL", - "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.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.label.nuGetServiceConnections": "Service connection credentials for feeds outside this organization", "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.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" } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1_Wif/main.ts b/_generated/NuGetAuthenticateV1_Wif/main.ts index e7aa94b8a429..517fa6f75aef 100644 --- a/_generated/NuGetAuthenticateV1_Wif/main.ts +++ b/_generated/NuGetAuthenticateV1_Wif/main.ts @@ -10,6 +10,10 @@ async function main(): Promise { let forceReinstallCredentialProvider = null; let federatedFeedAuthSuccessCount: number = 0; + var feedUrl; + var entraWifServiceConnectionName; + var serviceConnections; + try { tl.setResourcePath(path.join(__dirname, 'task.json')); @@ -17,38 +21,72 @@ async function main(): Promise { forceReinstallCredentialProvider = tl.getBoolInput("forceReinstallCredentialProvider", false); await installCredProviderToUserProfile(forceReinstallCredentialProvider); - const feedUrl = tl.getInput("feedUrl"); - const entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection"); + serviceConnections = getPackagingServiceConnections('nuGetServiceConnections'); + 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")); } // 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(); diff --git a/_generated/NuGetAuthenticateV1_Wif/task.json b/_generated/NuGetAuthenticateV1_Wif/task.json index 8ee3c9d01e4d..b77347ad0415 100644 --- a/_generated/NuGetAuthenticateV1_Wif/task.json +++ b/_generated/NuGetAuthenticateV1_Wif/task.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 1 }, "minimumAgentVersion": "2.144.0", @@ -31,15 +31,20 @@ "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", @@ -76,14 +81,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" } } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1_Wif/task.loc.json b/_generated/NuGetAuthenticateV1_Wif/task.loc.json index 1cbe01efeed7..cd83a4f26c76 100644 --- a/_generated/NuGetAuthenticateV1_Wif/task.loc.json +++ b/_generated/NuGetAuthenticateV1_Wif/task.loc.json @@ -13,7 +13,7 @@ ], "version": { "Major": 1, - "Minor": 262, + "Minor": 263, "Patch": 1 }, "minimumAgentVersion": "2.144.0", @@ -31,15 +31,20 @@ "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", @@ -76,14 +81,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" } } \ No newline at end of file diff --git a/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.json b/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.json index bb0bb163e775..234325ff52da 100644 --- a/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.json +++ b/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.json @@ -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", diff --git a/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.loc.json b/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.loc.json index 8667786cb42c..11c5a07b05b5 100644 --- a/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.loc.json +++ b/_generated/NuGetAuthenticateV1_Wif/taskJsonOverride.loc.json @@ -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",