Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent Non Collaborator Aliases Version 2: Using Permission API and Batch Call #6

Merged
merged 10 commits into from
Apr 19, 2024
73 changes: 55 additions & 18 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 52 additions & 18 deletions src/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,39 +163,72 @@ async function fetch_reviewers() {
return [ ...reviewers ];
}

async function assign_reviewers(reviewers) {
async function filter_only_collaborators(reviewers) {
const context = get_context();
const octokit = get_octokit();

const [ teams_with_prefix, individuals ] = partition(reviewers, (reviewer) => reviewer.startsWith('team:'));
const teams = teams_with_prefix.map((team_with_prefix) => team_with_prefix.replace('team:', ''));

const request_review_responses = [];

// Github's requestReviewers API will fail to add all reviewers if any of the aliases are not collaborators.
// Github also does not support a batch call to determine which aliases in the list are not collaborators.

// We therefore make each call individually so that we add all reviewers that are collaborators,
// and log failure for aliases that no longer have access.
// Create a list of requests for all available aliases and teams to see if they have permission
// to the PR associated with this action
const collaborator_responses = [];
teams.forEach((team) => {
request_review_responses.push(octokit.pulls.requestReviewers({
collaborator_responses.push(octokit.teams.checkPermissionsForRepoInOrg({
org: context.repo.owner,
team_slug: team,
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
team_reviewers: [ team ],
}).then((response) => {
// https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#check-team-permissions-for-a-repository
// Its expected that a team with permission will return 204
core.info(`Received successful status code ${response?.status ?? 'Unknown'} for team: ${team}`);
return 'team:'.concat(team);
}).catch((error) => core.error(`Team: ${team} failed to be added with error: ${error}`)));
});

individuals.forEach((login) => {
request_review_responses.push(octokit.pulls.requestReviewers({
individuals.forEach((alias) => {
collaborator_responses.push(octokit.repos.checkCollaborator({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
reviewers: [ login ],
}).catch((error) => core.error(`Individual: ${login} failed to be added with error: ${error}`)));
username: alias,
}).then((response) => {
// https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#check-if-a-user-is-a-repository-collaborator
// Its expected that a collaborator with permission will return 204
core.info(`Received successful status code ${response?.status ?? 'Unknown'} for alias: ${alias}`);
return alias;
}).catch((error) => core.error(`Individual: ${alias} failed to be added with error: ${error}`)));
});

// Store the aliases and teams of all successful responses
const collaborators = [];
await Promise.allSettled(collaborator_responses).then((results) => {
results.forEach((result) => {
if (result?.value) {
collaborators.push(result?.value);
}
});
});

return Promise.allSettled(request_review_responses);
// Only include aliases and teams that exist as collaborators
const filtered_reviewers = reviewers.filter((reviewer) => collaborators.includes(reviewer));
core.info(`Filtered list of only collaborators ${filtered_reviewers.join(', ')}`);
return filtered_reviewers;
}

async function assign_reviewers(reviewers) {
const context = get_context();
const octokit = get_octokit();

const [ teams_with_prefix, individuals ] = partition(reviewers, (reviewer) => reviewer.startsWith('team:'));
const teams = teams_with_prefix.map((team_with_prefix) => team_with_prefix.replace('team:', ''));

return octokit.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
reviewers: individuals,
team_reviewers: teams,
});
}

/* Private */
Expand Down Expand Up @@ -246,6 +279,7 @@ module.exports = {
fetch_config,
fetch_changed_files,
fetch_reviewers,
filter_only_collaborators,
assign_reviewers,
clear_cache,
};
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ async function run() {
core.info(`Possible Reviewers ${reviewers.join(', ')}, prepare filtering out already requested reviewers or approved reviewers`);
reviewers = reviewers.filter((reviewer) => !requested_approved_reviewers.includes(reviewer));

core.info(`Possible New Reviewers ${reviewers.join(', ')}, prepare to filter to only collaborators`);
reviewers = await github.filter_only_collaborators(reviewers);

core.info('Randomly picking reviewers if the number of reviewers is set');
reviewers = randomly_pick_reviewers({ reviewers, config });

Expand Down
Loading
Loading