Merge pull request #6539 from RoadieHQ/remove-github-restriction
remove repo filtering on the github credentials
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Remove repo restriction from GitHub credentials provider
|
||||
@@ -133,7 +133,7 @@ describe('GithubCredentialsProvider tests', () => {
|
||||
expect(token).toEqual('secret_token');
|
||||
});
|
||||
|
||||
it('should fail to issue tokens for an organization when the app is installed for a single repo', async () => {
|
||||
it('should not fail to issue tokens for an organization when the app is installed for a single repo', async () => {
|
||||
octokit.apps.listInstallations.mockResolvedValue({
|
||||
headers: {
|
||||
etag: '123',
|
||||
@@ -156,13 +156,12 @@ describe('GithubCredentialsProvider tests', () => {
|
||||
},
|
||||
} as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']);
|
||||
|
||||
await expect(
|
||||
github.getCredentials({
|
||||
url: 'https://github.com/backstage',
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
'The Backstage GitHub application used in the backstage organization must be installed for the entire organization to be able to issue credentials without a specified repository.',
|
||||
);
|
||||
const { token, headers } = await github.getCredentials({
|
||||
url: 'https://github.com/backstage',
|
||||
});
|
||||
const expectedToken = 'secret_token';
|
||||
expect(headers).toEqual({ Authorization: `Bearer ${expectedToken}` });
|
||||
expect(token).toEqual('secret_token');
|
||||
});
|
||||
|
||||
it('should throw if the app is suspended', async () => {
|
||||
|
||||
@@ -23,7 +23,6 @@ import { DateTime } from 'luxon';
|
||||
type InstallationData = {
|
||||
installationId: number;
|
||||
suspended: boolean;
|
||||
repositorySelection: 'selected' | 'all';
|
||||
};
|
||||
|
||||
class Cache {
|
||||
@@ -85,31 +84,34 @@ class GithubAppManager {
|
||||
owner: string,
|
||||
repo?: string,
|
||||
): Promise<{ accessToken: string }> {
|
||||
const { installationId, suspended, repositorySelection } =
|
||||
await this.getInstallationData(owner);
|
||||
const { installationId, suspended } = await this.getInstallationData(owner);
|
||||
if (suspended) {
|
||||
throw new Error(
|
||||
`The GitHub application for ${[owner, repo]
|
||||
.filter(Boolean)
|
||||
.join('/')} is suspended`,
|
||||
);
|
||||
}
|
||||
if (repositorySelection !== 'all' && !repo) {
|
||||
throw new Error(
|
||||
`The Backstage GitHub application used in the ${owner} organization must be installed for the entire organization to be able to issue credentials without a specified repository.`,
|
||||
);
|
||||
throw new Error(`The GitHub application for ${owner} is suspended`);
|
||||
}
|
||||
|
||||
const cacheKey = !repo ? owner : `${owner}/${repo}`;
|
||||
const repositories = repositorySelection !== 'all' ? [repo!] : undefined;
|
||||
const cacheKey = repo ? `${owner}/${repo}` : owner;
|
||||
|
||||
// Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.
|
||||
return this.cache.getOrCreateToken(cacheKey, async () => {
|
||||
const result = await this.appClient.apps.createInstallationAccessToken({
|
||||
installation_id: installationId,
|
||||
headers: HEADERS,
|
||||
repositories,
|
||||
});
|
||||
if (repo && result.data.repository_selection === 'selected') {
|
||||
const installationClient = new Octokit({
|
||||
auth: result.data.token,
|
||||
});
|
||||
const repos =
|
||||
await installationClient.apps.listReposAccessibleToInstallation();
|
||||
const hasRepo = repos.data.repositories.some(repository => {
|
||||
return repository.name === repo;
|
||||
});
|
||||
if (!hasRepo) {
|
||||
throw new Error(
|
||||
`The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
token: result.data.token,
|
||||
expiresAt: DateTime.fromISO(result.data.expires_at),
|
||||
@@ -132,7 +134,6 @@ class GithubAppManager {
|
||||
return {
|
||||
installationId: installation.id,
|
||||
suspended: Boolean(installation.suspended_by),
|
||||
repositorySelection: installation.repository_selection,
|
||||
};
|
||||
}
|
||||
const notFoundError = new Error(
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('GithubDiscoveryProcessor', () => {
|
||||
parseUrl('https://github.com/foo/proj/blob/master/catalog.yaml'),
|
||||
).toEqual({
|
||||
org: 'foo',
|
||||
host: 'github.com',
|
||||
repoSearchPath: /^proj$/,
|
||||
catalogPath: '/blob/master/catalog.yaml',
|
||||
});
|
||||
@@ -40,6 +41,7 @@ describe('GithubDiscoveryProcessor', () => {
|
||||
parseUrl('https://github.com/foo/proj*/blob/master/catalog.yaml'),
|
||||
).toEqual({
|
||||
org: 'foo',
|
||||
host: 'github.com',
|
||||
repoSearchPath: /^proj.*$/,
|
||||
catalogPath: '/blob/master/catalog.yaml',
|
||||
});
|
||||
|
||||
@@ -64,10 +64,18 @@ export class GithubDiscoveryProcessor implements CatalogProcessor {
|
||||
`There is no GitHub integration that matches ${location.target}. Please add a configuration entry for it under integrations.github`,
|
||||
);
|
||||
}
|
||||
|
||||
const { org, repoSearchPath, catalogPath, host } = parseUrl(
|
||||
location.target,
|
||||
);
|
||||
|
||||
// Building the org url here so that the github creds provider doesn't need to know
|
||||
// about how to handle the wild card which is special for this processor.
|
||||
const orgUrl = `https://${host}/${org}`;
|
||||
|
||||
const { headers } = await GithubCredentialsProvider.create(
|
||||
gitHubConfig,
|
||||
).getCredentials({ url: location.target });
|
||||
const { org, repoSearchPath, catalogPath } = parseUrl(location.target);
|
||||
).getCredentials({ url: orgUrl });
|
||||
|
||||
const client = graphql.defaults({
|
||||
baseUrl: gitHubConfig.apiBaseUrl,
|
||||
@@ -115,6 +123,7 @@ export function parseUrl(urlString: string): {
|
||||
org: string;
|
||||
repoSearchPath: RegExp;
|
||||
catalogPath: string;
|
||||
host: string;
|
||||
} {
|
||||
const url = new URL(urlString);
|
||||
const path = url.pathname.substr(1).split('/');
|
||||
@@ -125,6 +134,7 @@ export function parseUrl(urlString: string): {
|
||||
org: decodeURIComponent(path[0]),
|
||||
repoSearchPath: escapeRegExp(decodeURIComponent(path[1])),
|
||||
catalogPath: `/${decodeURIComponent(path.slice(2).join('/'))}`,
|
||||
host: url.host,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user