Merge pull request #6719 from ssaraswati/sonarqube-authenticate-calls

Add auth token to SonarQubeClient via IdentityApi
This commit is contained in:
Fredrik Adelöw
2021-08-09 13:57:49 +02:00
committed by GitHub
4 changed files with 114 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-sonarqube': minor
---
Use IdentityApi to provide Auth Token for SonarQubeClient Api calls
@@ -20,9 +20,39 @@ import { setupServer } from 'msw/node';
import { FindingSummary, SonarQubeClient } from './index';
import { ComponentWrapper, MeasuresWrapper } from './types';
import { UrlPatternDiscovery } from '@backstage/core-app-api';
import { IdentityApi } from '@backstage/core-plugin-api';
const server = setupServer();
const identityApiAuthenticated: IdentityApi = {
getUserId() {
return 'jane-fonda';
},
getProfile() {
return { email: 'jane-fonda@spotify.com' };
},
async getIdToken() {
return Promise.resolve('fake-id-token');
},
async signOut() {
return Promise.resolve();
},
};
const identityApiGuest: IdentityApi = {
getUserId() {
return 'guest';
},
getProfile() {
return {};
},
async getIdToken() {
return Promise.resolve(undefined);
},
async signOut() {
return Promise.resolve();
},
};
describe('SonarQubeClient', () => {
msw.setupDefaultHandlers(server);
@@ -161,7 +191,10 @@ describe('SonarQubeClient', () => {
it('should report finding summary', async () => {
setupHandlers();
const client = new SonarQubeClient({ discoveryApi });
const client = new SonarQubeClient({
discoveryApi,
identityApi: identityApiAuthenticated,
});
const summary = await client.getFindingSummary('our:service');
expect(summary).toEqual(
@@ -197,6 +230,7 @@ describe('SonarQubeClient', () => {
const client = new SonarQubeClient({
discoveryApi,
baseUrl: 'http://a.instance.local',
identityApi: identityApiAuthenticated,
});
const summary = await client.getFindingSummary('our:service');
@@ -234,6 +268,7 @@ describe('SonarQubeClient', () => {
const client = new SonarQubeClient({
discoveryApi,
baseUrl: 'http://a.instance.local',
identityApi: identityApiAuthenticated,
});
const summary = await client.getFindingSummary('our:service');
@@ -255,4 +290,56 @@ describe('SonarQubeClient', () => {
'http://a.instance.local/component_measures?id=our%3Aservice&metric=coverage&resolved=false&view=list',
);
});
it('should add identity token for logged in users', async () => {
setupHandlers();
server.use(
rest.get(`${mockBaseUrl}/sonarqube/components/show`, (req, res, ctx) => {
expect(req.url.searchParams.toString()).toBe('component=our%3Aservice');
expect(req.headers.get('Authorization')).toBe('Bearer fake-id-token');
return res(
ctx.json({
component: {
analysisDate: '2020-01-01T00:00:00Z',
},
} as ComponentWrapper),
);
}),
);
const client = new SonarQubeClient({
discoveryApi,
baseUrl: 'http://a.instance.local',
identityApi: identityApiAuthenticated,
});
const summary = await client.getFindingSummary('our:service');
expect(summary?.lastAnalysis).toBe('2020-01-01T00:00:00Z');
});
it('should omit identity token for guest users', async () => {
setupHandlers();
server.use(
rest.get(`${mockBaseUrl}/sonarqube/components/show`, (req, res, ctx) => {
expect(req.url.searchParams.toString()).toBe('component=our%3Aservice');
expect(req.headers.has('Authorization')).toBeFalsy();
return res(
ctx.json({
component: {
analysisDate: '2020-01-01T00:00:00Z',
},
} as ComponentWrapper),
);
}),
);
const client = new SonarQubeClient({
discoveryApi,
baseUrl: 'http://a.instance.local',
identityApi: identityApiGuest,
});
const summary = await client.getFindingSummary('our:service');
expect(summary?.lastAnalysis).toBe('2020-01-01T00:00:00Z');
});
});
+13 -1
View File
@@ -17,20 +17,24 @@
import fetch from 'cross-fetch';
import { FindingSummary, Metrics, SonarQubeApi } from './SonarQubeApi';
import { ComponentWrapper, MeasuresWrapper } from './types';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
export class SonarQubeClient implements SonarQubeApi {
discoveryApi: DiscoveryApi;
baseUrl: string;
identityApi: IdentityApi;
constructor({
discoveryApi,
identityApi,
baseUrl = 'https://sonarcloud.io/',
}: {
discoveryApi: DiscoveryApi;
identityApi: IdentityApi;
baseUrl?: string;
}) {
this.discoveryApi = discoveryApi;
this.identityApi = identityApi;
this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
}
@@ -38,9 +42,17 @@ export class SonarQubeClient implements SonarQubeApi {
path: string,
query: { [key in string]: any },
): Promise<T | undefined> {
const idToken = await this.identityApi.getIdToken();
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`;
const response = await fetch(
`${apiUrl}/${path}?${new URLSearchParams(query).toString()}`,
{
headers: {
'Content-Type': 'application/json',
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
},
);
if (response.status === 200) {
return (await response.json()) as T;
+8 -2
View File
@@ -21,6 +21,7 @@ import {
createComponentExtension,
createPlugin,
discoveryApiRef,
identityApiRef,
} from '@backstage/core-plugin-api';
export const sonarQubePlugin = createPlugin({
@@ -28,11 +29,16 @@ export const sonarQubePlugin = createPlugin({
apis: [
createApiFactory({
api: sonarQubeApiRef,
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
factory: ({ configApi, discoveryApi }) =>
deps: {
configApi: configApiRef,
discoveryApi: discoveryApiRef,
identityApi: identityApiRef,
},
factory: ({ configApi, discoveryApi, identityApi }) =>
new SonarQubeClient({
discoveryApi,
baseUrl: configApi.getOptionalString('sonarQube.baseUrl'),
identityApi,
}),
}),
],