Skip to content

Commit

Permalink
Bugfix: Match service connection using feedId/ProjID (#19732)
Browse files Browse the repository at this point in the history
* Bugfix: Parse service connection using feedId/ProjID

* Bump task version

* Add Type matching

Co-authored-by: Jonathan Myers <11822817+jmyersmsft@users.noreply.github.com>

* updated generated files

* Verifying service connection matches feed

* Limit search to subset of service connections

* Bump task version

* Removed unused var

* Applied review suggestions

* rebuild / removed unused + comment

---------

Co-authored-by: Jonathan Myers <11822817+jmyersmsft@users.noreply.github.com>
  • Loading branch information
AlexVTor and jmyersmsft authored Apr 9, 2024
1 parent 5f964db commit 8aaa591
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 46 deletions.
64 changes: 54 additions & 10 deletions Tasks/NuGetCommandV2/nugetpublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as vstsNuGetPushToolRunner from "./Common/VstsNuGetPushToolRunner";
import * as vstsNuGetPushToolUtilities from "./Common/VstsNuGetPushToolUtilities";
import { getProjectAndFeedIdFromInputParam } from 'azure-pipelines-tasks-packaging-common/util';
import { logError } from 'azure-pipelines-tasks-packaging-common/util';
import { WebRequest, WebResponse, sendRequest } from 'azure-pipelines-tasks-utility-common/restutilities';


class PublishOptions implements INuGetCommandOptions {
constructor(
Expand Down Expand Up @@ -112,7 +114,7 @@ export async function run(nuGetPath: string): Promise<void> {
let accessToken;
let feed;
const isInternalFeed: boolean = nugetFeedType === "internal";
accessToken = getAccessToken(isInternalFeed);
accessToken = await getAccessToken(isInternalFeed, urlPrefixes);
const quirks = await ngToolRunner.getNuGetQuirksAsync(nuGetPath);

// Clauses ordered in this way to avoid short-circuit evaluation, so the debug info printed by the functions
Expand Down Expand Up @@ -414,9 +416,9 @@ function shouldUseVstsNuGetPush(isInternalFeed: boolean, conflictsAllowed: boole
return false;
}

function getAccessToken(isInternalFeed: boolean): string{
let accessToken: string;
async function getAccessToken(isInternalFeed: boolean, uriPrefixes: any): Promise<string>{
let allowServiceConnection = tl.getVariable('PUBLISH_VIA_SERVICE_CONNECTION');
let accessToken: string;

if(allowServiceConnection) {
let endpoint = tl.getInput('externalEndpoint', false);
Expand All @@ -440,16 +442,25 @@ function getAccessToken(isInternalFeed: boolean): string{
tl.debug("Checking for auth from Cred Provider.");
const feed = getProjectAndFeedIdFromInputParam('feedPublish');
const JsonEndpointsString = process.env["VSS_NUGET_EXTERNAL_FEED_ENDPOINTS"];

if (JsonEndpointsString) {
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);
tl.debug(`Endpoints found: ${JsonEndpointsString}`);

let endpointsArray: { endpointCredentials: EndpointCredentials[] } = JSON.parse(JsonEndpointsString);
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);

for (let endpoint_in = 0; endpoint_in < endpointsArray.endpointCredentials.length; endpoint_in++) {
if (endpointsArray.endpointCredentials[endpoint_in].endpoint.search(feed.feedName) != -1) {
tl.debug(`Endpoint Credentials found for ${feed.feedName}`);
accessToken = endpointsArray.endpointCredentials[endpoint_in].password;
let matchingEndpoint: EndpointCredentials;
for (const e of endpointsArray.endpointCredentials) {
for (const prefix of uriPrefixes) {
if (e.endpoint.toUpperCase().startsWith(prefix.toUpperCase())) {
let isServiceConnectionValid = await tryServiceConnection(e, feed);
if (isServiceConnectionValid) {
matchingEndpoint = e;
break;
}
}
}
if (matchingEndpoint) {
accessToken = matchingEndpoint.password;
break;
}
}
Expand All @@ -466,4 +477,37 @@ function getAccessToken(isInternalFeed: boolean): string{
}

return accessToken;
}
}

async function tryServiceConnection(endpoint: EndpointCredentials, feed: any) : Promise<boolean>
{
// Create request
const request = new WebRequest();
const token64 = Buffer.from(`${endpoint.username}:${endpoint.password}`).toString('base64');
request.uri = endpoint.endpoint;
request.method = 'GET';
request.headers = {
"Content-Type": "application/json",
"Authorization": "Basic " + token64
};

const response = await sendRequest(request);

if(response.statusCode == 200) {
if(response.body) {
for (const entry of response.body.resources) {
if (entry['@type'] === 'AzureDevOpsProjectId' && entry['label'].toUpperCase() !== feed.projectId.toUpperCase())
{
return false;
}
if (entry['@type'] === 'VssFeedId' && entry['label'].toUpperCase() !== feed.feedId.toUpperCase())
{
return false;
}
}
// We found matches in feedId and projectId, return the service connection
return true;
}
}
return false;
}
2 changes: 1 addition & 1 deletion Tasks/NuGetCommandV2/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 2,
"Minor": 238,
"Patch": 0
"Patch": 1
},
"runsOn": [
"Agent",
Expand Down
2 changes: 1 addition & 1 deletion Tasks/NuGetCommandV2/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 2,
"Minor": 238,
"Patch": 0
"Patch": 1
},
"runsOn": [
"Agent",
Expand Down
4 changes: 2 additions & 2 deletions _generated/NuGetCommandV2.versionmap.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Default|2.238.0
Node20_229_1|2.238.1
Default|2.238.1
Node20_229_1|2.238.2
64 changes: 54 additions & 10 deletions _generated/NuGetCommandV2/nugetpublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as vstsNuGetPushToolRunner from "./Common/VstsNuGetPushToolRunner";
import * as vstsNuGetPushToolUtilities from "./Common/VstsNuGetPushToolUtilities";
import { getProjectAndFeedIdFromInputParam } from 'azure-pipelines-tasks-packaging-common/util';
import { logError } from 'azure-pipelines-tasks-packaging-common/util';
import { WebRequest, WebResponse, sendRequest } from 'azure-pipelines-tasks-utility-common/restutilities';


class PublishOptions implements INuGetCommandOptions {
constructor(
Expand Down Expand Up @@ -112,7 +114,7 @@ export async function run(nuGetPath: string): Promise<void> {
let accessToken;
let feed;
const isInternalFeed: boolean = nugetFeedType === "internal";
accessToken = getAccessToken(isInternalFeed);
accessToken = await getAccessToken(isInternalFeed, urlPrefixes);
const quirks = await ngToolRunner.getNuGetQuirksAsync(nuGetPath);

// Clauses ordered in this way to avoid short-circuit evaluation, so the debug info printed by the functions
Expand Down Expand Up @@ -414,9 +416,9 @@ function shouldUseVstsNuGetPush(isInternalFeed: boolean, conflictsAllowed: boole
return false;
}

function getAccessToken(isInternalFeed: boolean): string{
let accessToken: string;
async function getAccessToken(isInternalFeed: boolean, uriPrefixes: any): Promise<string>{
let allowServiceConnection = tl.getVariable('PUBLISH_VIA_SERVICE_CONNECTION');
let accessToken: string;

if(allowServiceConnection) {
let endpoint = tl.getInput('externalEndpoint', false);
Expand All @@ -440,16 +442,25 @@ function getAccessToken(isInternalFeed: boolean): string{
tl.debug("Checking for auth from Cred Provider.");
const feed = getProjectAndFeedIdFromInputParam('feedPublish');
const JsonEndpointsString = process.env["VSS_NUGET_EXTERNAL_FEED_ENDPOINTS"];

if (JsonEndpointsString) {
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);
tl.debug(`Endpoints found: ${JsonEndpointsString}`);

let endpointsArray: { endpointCredentials: EndpointCredentials[] } = JSON.parse(JsonEndpointsString);
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);

for (let endpoint_in = 0; endpoint_in < endpointsArray.endpointCredentials.length; endpoint_in++) {
if (endpointsArray.endpointCredentials[endpoint_in].endpoint.search(feed.feedName) != -1) {
tl.debug(`Endpoint Credentials found for ${feed.feedName}`);
accessToken = endpointsArray.endpointCredentials[endpoint_in].password;
let matchingEndpoint: EndpointCredentials;
for (const e of endpointsArray.endpointCredentials) {
for (const prefix of uriPrefixes) {
if (e.endpoint.toUpperCase().startsWith(prefix.toUpperCase())) {
let isServiceConnectionValid = await tryServiceConnection(e, feed);
if (isServiceConnectionValid) {
matchingEndpoint = e;
break;
}
}
}
if (matchingEndpoint) {
accessToken = matchingEndpoint.password;
break;
}
}
Expand All @@ -466,4 +477,37 @@ function getAccessToken(isInternalFeed: boolean): string{
}

return accessToken;
}
}

async function tryServiceConnection(endpoint: EndpointCredentials, feed: any) : Promise<boolean>
{
// Create request
const request = new WebRequest();
const token64 = Buffer.from(`${endpoint.username}:${endpoint.password}`).toString('base64');
request.uri = endpoint.endpoint;
request.method = 'GET';
request.headers = {
"Content-Type": "application/json",
"Authorization": "Basic " + token64
};

const response = await sendRequest(request);

if(response.statusCode == 200) {
if(response.body) {
for (const entry of response.body.resources) {
if (entry['@type'] === 'AzureDevOpsProjectId' && entry['label'].toUpperCase() !== feed.projectId.toUpperCase())
{
return false;
}
if (entry['@type'] === 'VssFeedId' && entry['label'].toUpperCase() !== feed.feedId.toUpperCase())
{
return false;
}
}
// We found matches in feedId and projectId, return the service connection
return true;
}
}
return false;
}
6 changes: 3 additions & 3 deletions _generated/NuGetCommandV2/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 2,
"Minor": 238,
"Patch": 0
"Patch": 1
},
"runsOn": [
"Agent",
Expand Down Expand Up @@ -546,7 +546,7 @@
"Warning_UnsupportedServiceConnectionAuth": "The service connection does not use a supported authentication method. Please use a service connection with personal access token based auth."
},
"_buildConfigMapping": {
"Default": "2.238.0",
"Node20_229_1": "2.238.1"
"Default": "2.238.1",
"Node20_229_1": "2.238.2"
}
}
6 changes: 3 additions & 3 deletions _generated/NuGetCommandV2/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 2,
"Minor": 238,
"Patch": 0
"Patch": 1
},
"runsOn": [
"Agent",
Expand Down Expand Up @@ -546,7 +546,7 @@
"Warning_UnsupportedServiceConnectionAuth": "ms-resource:loc.messages.Warning_UnsupportedServiceConnectionAuth"
},
"_buildConfigMapping": {
"Default": "2.238.0",
"Node20_229_1": "2.238.1"
"Default": "2.238.1",
"Node20_229_1": "2.238.2"
}
}
64 changes: 54 additions & 10 deletions _generated/NuGetCommandV2_Node20/nugetpublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as vstsNuGetPushToolRunner from "./Common/VstsNuGetPushToolRunner";
import * as vstsNuGetPushToolUtilities from "./Common/VstsNuGetPushToolUtilities";
import { getProjectAndFeedIdFromInputParam } from 'azure-pipelines-tasks-packaging-common/util';
import { logError } from 'azure-pipelines-tasks-packaging-common/util';
import { WebRequest, WebResponse, sendRequest } from 'azure-pipelines-tasks-utility-common/restutilities';


class PublishOptions implements INuGetCommandOptions {
constructor(
Expand Down Expand Up @@ -112,7 +114,7 @@ export async function run(nuGetPath: string): Promise<void> {
let accessToken;
let feed;
const isInternalFeed: boolean = nugetFeedType === "internal";
accessToken = getAccessToken(isInternalFeed);
accessToken = await getAccessToken(isInternalFeed, urlPrefixes);
const quirks = await ngToolRunner.getNuGetQuirksAsync(nuGetPath);

// Clauses ordered in this way to avoid short-circuit evaluation, so the debug info printed by the functions
Expand Down Expand Up @@ -414,9 +416,9 @@ function shouldUseVstsNuGetPush(isInternalFeed: boolean, conflictsAllowed: boole
return false;
}

function getAccessToken(isInternalFeed: boolean): string{
let accessToken: string;
async function getAccessToken(isInternalFeed: boolean, uriPrefixes: any): Promise<string>{
let allowServiceConnection = tl.getVariable('PUBLISH_VIA_SERVICE_CONNECTION');
let accessToken: string;

if(allowServiceConnection) {
let endpoint = tl.getInput('externalEndpoint', false);
Expand All @@ -440,16 +442,25 @@ function getAccessToken(isInternalFeed: boolean): string{
tl.debug("Checking for auth from Cred Provider.");
const feed = getProjectAndFeedIdFromInputParam('feedPublish');
const JsonEndpointsString = process.env["VSS_NUGET_EXTERNAL_FEED_ENDPOINTS"];

if (JsonEndpointsString) {
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);
tl.debug(`Endpoints found: ${JsonEndpointsString}`);

let endpointsArray: { endpointCredentials: EndpointCredentials[] } = JSON.parse(JsonEndpointsString);
tl.debug(`Feed details ${feed.feedId} ${feed.projectId}`);

for (let endpoint_in = 0; endpoint_in < endpointsArray.endpointCredentials.length; endpoint_in++) {
if (endpointsArray.endpointCredentials[endpoint_in].endpoint.search(feed.feedName) != -1) {
tl.debug(`Endpoint Credentials found for ${feed.feedName}`);
accessToken = endpointsArray.endpointCredentials[endpoint_in].password;
let matchingEndpoint: EndpointCredentials;
for (const e of endpointsArray.endpointCredentials) {
for (const prefix of uriPrefixes) {
if (e.endpoint.toUpperCase().startsWith(prefix.toUpperCase())) {
let isServiceConnectionValid = await tryServiceConnection(e, feed);
if (isServiceConnectionValid) {
matchingEndpoint = e;
break;
}
}
}
if (matchingEndpoint) {
accessToken = matchingEndpoint.password;
break;
}
}
Expand All @@ -466,4 +477,37 @@ function getAccessToken(isInternalFeed: boolean): string{
}

return accessToken;
}
}

async function tryServiceConnection(endpoint: EndpointCredentials, feed: any) : Promise<boolean>
{
// Create request
const request = new WebRequest();
const token64 = Buffer.from(`${endpoint.username}:${endpoint.password}`).toString('base64');
request.uri = endpoint.endpoint;
request.method = 'GET';
request.headers = {
"Content-Type": "application/json",
"Authorization": "Basic " + token64
};

const response = await sendRequest(request);

if(response.statusCode == 200) {
if(response.body) {
for (const entry of response.body.resources) {
if (entry['@type'] === 'AzureDevOpsProjectId' && entry['label'].toUpperCase() !== feed.projectId.toUpperCase())
{
return false;
}
if (entry['@type'] === 'VssFeedId' && entry['label'].toUpperCase() !== feed.feedId.toUpperCase())
{
return false;
}
}
// We found matches in feedId and projectId, return the service connection
return true;
}
}
return false;
}
6 changes: 3 additions & 3 deletions _generated/NuGetCommandV2_Node20/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 2,
"Minor": 238,
"Patch": 1
"Patch": 2
},
"runsOn": [
"Agent",
Expand Down Expand Up @@ -550,7 +550,7 @@
"Warning_UnsupportedServiceConnectionAuth": "The service connection does not use a supported authentication method. Please use a service connection with personal access token based auth."
},
"_buildConfigMapping": {
"Default": "2.238.0",
"Node20_229_1": "2.238.1"
"Default": "2.238.1",
"Node20_229_1": "2.238.2"
}
}
Loading

0 comments on commit 8aaa591

Please sign in to comment.