Merge pull request #25454 from elaine-mattos/feat/allow-oauth-fetch-action

GitlabUrlReader: Allow fetch:plain Scaffolder action to use user-provided token
This commit is contained in:
Ben Lambert
2024-07-17 16:40:15 +02:00
committed by GitHub
5 changed files with 84 additions and 23 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/integration': minor
'@backstage/backend-defaults': patch
---
Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens.
@@ -23,21 +23,21 @@ import {
UrlReaderServiceSearchOptions,
UrlReaderServiceSearchResponse,
} from '@backstage/backend-plugin-api';
import { NotFoundError, NotModifiedError } from '@backstage/errors';
import {
GitLabIntegration,
ScmIntegrations,
getGitLabFileFetchUrl,
getGitLabIntegrationRelativePath,
getGitLabRequestOptions,
GitLabIntegration,
ScmIntegrations,
} from '@backstage/integration';
import fetch, { Response } from 'node-fetch';
import parseGitUrl from 'git-url-parse';
import { Minimatch } from 'minimatch';
import { Readable } from 'stream';
import { NotFoundError, NotModifiedError } from '@backstage/errors';
import { ReadTreeResponseFactory, ReaderFactory } from './types';
import { trimEnd, trimStart } from 'lodash';
import { Minimatch } from 'minimatch';
import fetch, { Response } from 'node-fetch';
import { Readable } from 'stream';
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
import { ReadTreeResponseFactory, ReaderFactory } from './types';
import { parseLastModified } from './util';
/**
@@ -71,14 +71,14 @@ export class GitlabUrlReader implements UrlReaderService {
url: string,
options?: UrlReaderServiceReadUrlOptions,
): Promise<UrlReaderServiceReadUrlResponse> {
const { etag, lastModifiedAfter, signal } = options ?? {};
const { etag, lastModifiedAfter, signal, token } = options ?? {};
const builtUrl = await this.getGitlabFetchUrl(url);
let response: Response;
try {
response = await fetch(builtUrl, {
headers: {
...getGitLabRequestOptions(this.integration.config).headers,
...getGitLabRequestOptions(this.integration.config, token).headers,
...(etag && { 'If-None-Match': etag }),
...(lastModifiedAfter && {
'If-Modified-Since': lastModifiedAfter.toUTCString(),
@@ -120,7 +120,7 @@ export class GitlabUrlReader implements UrlReaderService {
url: string,
options?: UrlReaderServiceReadTreeOptions,
): Promise<UrlReaderServiceReadTreeResponse> {
const { etag, signal } = options ?? {};
const { etag, signal, token } = options ?? {};
const { ref, full_name, filepath } = parseGitUrl(url);
let repoFullName = full_name;
@@ -147,7 +147,7 @@ export class GitlabUrlReader implements UrlReaderService {
repoFullName,
)}`,
).toString(),
getGitLabRequestOptions(this.integration.config),
getGitLabRequestOptions(this.integration.config, token),
);
if (!projectGitlabResponse.ok) {
const msg = `Failed to read tree from ${url}, ${projectGitlabResponse.status} ${projectGitlabResponse.statusText}`;
@@ -175,7 +175,7 @@ export class GitlabUrlReader implements UrlReaderService {
)}/repository/commits?${commitsReqParams.toString()}`,
).toString(),
{
...getGitLabRequestOptions(this.integration.config),
...getGitLabRequestOptions(this.integration.config, token),
// TODO(freben): The signal cast is there because pre-3.x versions of
// node-fetch have a very slightly deviating AbortSignal type signature.
// The difference does not affect us in practice however. The cast can
@@ -209,7 +209,7 @@ export class GitlabUrlReader implements UrlReaderService {
repoFullName,
)}/repository/archive?${archiveReqParams.toString()}`,
{
...getGitLabRequestOptions(this.integration.config),
...getGitLabRequestOptions(this.integration.config, token),
// TODO(freben): The signal cast is there because pre-3.x versions of
// node-fetch have a very slightly deviating AbortSignal type signature.
// The difference does not affect us in practice however. The cast can
+4 -1
View File
@@ -508,7 +508,10 @@ export function getGitLabIntegrationRelativePath(
): string;
// @public
export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
export function getGitLabRequestOptions(
config: GitLabIntegrationConfig,
token?: string,
): {
headers: Record<string, string>;
};
+44 -1
View File
@@ -17,7 +17,7 @@
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { GitLabIntegrationConfig } from './config';
import { getGitLabFileFetchUrl } from './core';
import { getGitLabFileFetchUrl, getGitLabRequestOptions } from './core';
const worker = setupServer();
@@ -186,4 +186,47 @@ describe('gitlab core', () => {
});
});
});
describe('getGitLabRequestOptions', () => {
it('should return Authorization header when oauthToken is provided', () => {
const token = '1234567890';
const result = getGitLabRequestOptions(
configSelfHosteWithRelativePath,
token,
);
expect(result).toEqual({
headers: {
Authorization: `Bearer ${token}`,
},
});
});
it('should return private-token header when gl-token is provided', () => {
const token = 'glpat-1234566';
const result = getGitLabRequestOptions(
configSelfHosteWithRelativePath,
token,
);
expect(result).toEqual({
headers: {
'PRIVATE-TOKEN': token,
},
});
});
it('should return private-token header when oauthToken is undefined', () => {
const oauthToken = undefined;
const result = getGitLabRequestOptions(
configSelfHosteWithRelativePath,
oauthToken,
);
expect(result).toEqual({
headers: {
'PRIVATE-TOKEN': configSelfHosteWithRelativePath.token,
},
});
});
});
});
+17 -8
View File
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import fetch from 'cross-fetch';
import {
getGitLabIntegrationRelativePath,
GitLabIntegrationConfig,
} from './config';
import fetch from 'cross-fetch';
/**
* Given a URL pointing to a file on a provider, returns a URL that is suitable
@@ -51,14 +51,23 @@ export async function getGitLabFileFetchUrl(
* @param config - The relevant provider config
* @public
*/
export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
headers: Record<string, string>;
} {
const { token = '' } = config;
export function getGitLabRequestOptions(
config: GitLabIntegrationConfig,
token?: string,
): { headers: Record<string, string> } {
if (token) {
// If token comes from the user and starts with "gl", it's a private token (see https://docs.gitlab.com/ee/security/token_overview.html#token-prefixes)
return {
headers: token.startsWith('gl')
? { 'PRIVATE-TOKEN': token }
: { Authorization: `Bearer ${token}` }, // Otherwise, it's a bearer token
};
}
// If token not provided, fetch the integration token
const { token: configToken = '' } = config;
return {
headers: {
'PRIVATE-TOKEN': token,
},
headers: { 'PRIVATE-TOKEN': configToken },
};
}