Fix authorization header for GitLab requests

Signed-off-by: Cptn Fizzbin <code@cptnfizzbin.ca>
This commit is contained in:
Cptn Fizzbin
2025-06-06 09:56:34 -04:00
parent 5247c13d74
commit e0189b811d
5 changed files with 50 additions and 57 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-defaults': patch
'@backstage/integration': patch
---
UrlReader: Fix handling of access tokens for GitLab readURL requests
@@ -108,9 +108,6 @@ describe('GitlabUrlReader', () => {
config: createConfig(),
response: expect.objectContaining({
url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
headers: expect.objectContaining({
'private-token': '',
}),
}),
},
{
@@ -119,7 +116,7 @@ describe('GitlabUrlReader', () => {
response: expect.objectContaining({
url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
headers: expect.objectContaining({
'private-token': '0123456789',
authorization: 'Bearer 0123456789',
}),
}),
},
@@ -252,10 +249,10 @@ describe('GitlabUrlReader', () => {
it('should return the file when using a user token', async () => {
worker.use(
rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
if (req.headers.get('private-token') !== 'gl-user-token') {
if (req.headers.get('authorization') !== 'Bearer gl-user-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
@@ -571,10 +568,10 @@ describe('GitlabUrlReader', () => {
it('should return the file when using a user token', async () => {
worker.use(
rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
if (req.headers.get('private-token') !== 'gl-user-token') {
if (req.headers.get('authorization') !== 'Bearer gl-user-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
@@ -737,10 +734,10 @@ describe('GitlabUrlReader', () => {
(_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })),
),
rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
if (req.headers.get('private-token') !== 'gl-user-token') {
if (req.headers.get('authorization') !== 'Bearer gl-user-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
@@ -798,10 +795,10 @@ describe('GitlabUrlReader', () => {
(_, res, ctx) => res(ctx.status(404)),
),
rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
if (req.headers.get('private-token') !== 'gl-user-token') {
if (req.headers.get('authorization') !== 'Bearer gl-user-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
@@ -857,21 +854,19 @@ describe('GitlabUrlReader', () => {
beforeEach(() => {
worker.use(
rest.get('*/api/v4/projects/group%2Fproject', (req, res, ctx) => {
// the private-token header must be included on API calls
if (req.headers.get('private-token') !== 'gl-dummy-token') {
if (req.headers.get('authorization') !== 'Bearer gl-dummy-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
}),
rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
// the private-token header must be included on API calls
if (req.headers.get('private-token') !== 'gl-user-token') {
if (req.headers.get('authorization') !== 'Bearer gl-user-token') {
return res(
ctx.status(403),
ctx.json({ message: 'Not Authorized' }),
ctx.status(401),
ctx.json({ message: '401 Unauthorized' }),
);
}
return res(ctx.status(200), ctx.json({ id: 12345 }));
@@ -398,6 +398,12 @@ export class GitlabUrlReader implements UrlReaderService {
);
const data = await result.json();
if (!result.ok) {
if (result.status === 401) {
throw new Error(
'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project',
);
}
throw new Error(`Gitlab error: ${data.error}, ${data.error_description}`);
}
return Number(data.id);
+4 -21
View File
@@ -188,7 +188,7 @@ describe('gitlab core', () => {
});
describe('getGitLabRequestOptions', () => {
it('should return Authorization header when oauthToken is provided', () => {
it('should return Authorization bearer header when a token is provided', () => {
const token = '1234567890';
const result = getGitLabRequestOptions(
configSelfHosteWithRelativePath,
@@ -202,29 +202,12 @@ describe('gitlab core', () => {
});
});
it('should return private-token header when gl-token is provided', () => {
const token = 'glpat-1234566';
const result = getGitLabRequestOptions(
configSelfHosteWithRelativePath,
token,
);
it('should return Authorization bearer header using the config token when no token is provided', () => {
const result = getGitLabRequestOptions(configSelfHosteWithRelativePath);
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,
Authorization: `Bearer ${configSelfHosteWithRelativePath.token}`,
},
});
});
+15 -12
View File
@@ -56,20 +56,17 @@ 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
};
const headers: Record<string, string> = {};
const accessToken = token || config.token;
if (accessToken) {
// OAuth, Personal, Project, and Group access tokens can all be passed via
// a bearer authorization header
// https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens
headers.Authorization = `Bearer ${accessToken}`;
}
// If token not provided, fetch the integration token
const { token: configToken = '' } = config;
return {
headers: { 'PRIVATE-TOKEN': configToken },
};
return { headers };
}
// Converts
@@ -149,6 +146,12 @@ export async function getProjectId(
const data = await response.json();
if (!response.ok) {
if (response.status === 401) {
throw new Error(
'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project',
);
}
throw new Error(
`GitLab Error '${data.error}', ${data.error_description}`,
);