From 3ee666580ab0296d99faaffe4643eddcf564940d Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 25 Jun 2020 00:40:15 +0200 Subject: [PATCH] fix(dev-infra): merge script should not always require full repo permissions (#37718) We recently added OAuth scope checking to the dev-infra Git client and started leveraging it for the merge script. We set the `repo` scope as required for running the merge script. We can loosen this requirement as in the Angular org where the script is consumed, only pull requests on public repositories are merged through the script. This should help with reducing the risk with compromised tokens as no access had to be granted on `repo:invite`, `repo_deployment` etc. PR Close #37718 --- dev-infra/pr/merge/task.ts | 18 +++++++++++++----- dev-infra/utils/config.ts | 2 ++ dev-infra/utils/git/index.ts | 14 +++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/dev-infra/pr/merge/task.ts b/dev-infra/pr/merge/task.ts index 5a17784738..7b9a76ead2 100644 --- a/dev-infra/pr/merge/task.ts +++ b/dev-infra/pr/merge/task.ts @@ -16,9 +16,6 @@ import {isPullRequest, loadAndValidatePullRequest,} from './pull-request'; import {GithubApiMergeStrategy} from './strategies/api-merge'; import {AutosquashMergeStrategy} from './strategies/autosquash-merge'; -/** Github OAuth scopes required for the merge task. */ -const REQUIRED_SCOPES = ['repo']; - /** Describes the status of a pull request merge. */ export const enum MergeStatus { UNKNOWN_GIT_ERROR, @@ -56,8 +53,19 @@ export class PullRequestMergeTask { * @param force Whether non-critical pull request failures should be ignored. */ async merge(prNumber: number, force = false): Promise { - // Assert the authenticated GitClient has access on the required scopes. - const hasOauthScopes = await this.git.hasOauthScopes(...REQUIRED_SCOPES); + // Check whether the given Github token has sufficient permissions for writing + // to the configured repository. If the repository is not private, only the + // reduced `public_repo` OAuth scope is sufficient for performing merges. + const hasOauthScopes = await this.git.hasOauthScopes((scopes, missing) => { + if (!scopes.includes('repo')) { + if (this.config.remote.private) { + missing.push('repo'); + } else if (!scopes.includes('public_repo')) { + missing.push('public_repo'); + } + } + }); + if (hasOauthScopes !== true) { return { status: MergeStatus.GITHUB_ERROR, diff --git a/dev-infra/utils/config.ts b/dev-infra/utils/config.ts index bcf6a82d60..bd08ee68e4 100644 --- a/dev-infra/utils/config.ts +++ b/dev-infra/utils/config.ts @@ -21,6 +21,8 @@ export interface GitClientConfig { name: string; /** If SSH protocol should be used for git interactions. */ useSsh?: boolean; + /** Whether the specified repository is private. */ + private?: boolean; } /** diff --git a/dev-infra/utils/git/index.ts b/dev-infra/utils/git/index.ts index 3a15b5a098..88c626dc20 100644 --- a/dev-infra/utils/git/index.ts +++ b/dev-infra/utils/git/index.ts @@ -21,6 +21,9 @@ type RateLimitResponseWithOAuthScopeHeader = Octokit.Response void; + /** Error for failed Git commands. */ export class GitCommandError extends Error { constructor(client: GitClient, public args: string[]) { @@ -155,14 +158,11 @@ export class GitClient { * Assert the GitClient instance is using a token with permissions for the all of the * provided OAuth scopes. */ - async hasOauthScopes(...requestedScopes: string[]): Promise { - const missingScopes: string[] = []; + async hasOauthScopes(testFn: OAuthScopeTestFunction): Promise { const scopes = await this.getAuthScopesForToken(); - requestedScopes.forEach(scope => { - if (!scopes.includes(scope)) { - missingScopes.push(scope); - } - }); + const missingScopes: string[] = []; + // Test Github OAuth scopes and collect missing ones. + testFn(scopes, missingScopes); // If no missing scopes are found, return true to indicate all OAuth Scopes are available. if (missingScopes.length === 0) { return true;