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:
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user