Skip to content

Commit

Permalink
add optional authorized team configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
robandpdx committed Dec 8, 2023
1 parent 8f09eec commit b5bf6bf
Show file tree
Hide file tree
Showing 4 changed files with 500 additions and 19 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ You will need to decide the label that this app looks for, the contents of the i
- `ISSUE_BODY_FILE` This is the file containing the body of the issue created
- `ISSUE_ASSIGNEES` This is a comma separated list of the issue assignees

Optionally, you can define a team name in the `AUTHORIZED_TEAM` variable. The app will consider members of this team authorized to use this app. The app will add a comment on the PR if an user who is not a member of this team attemps to do any of the following:
- Opens a PR with the `TRIGGER_STRING` in the body
- Adds a PR comment with the `TRIGGER_STRING` in the body
- Adds the `EMERGENCY_LABEL` to a PR

The comment will read:
```
@username is not authorized to apply the emergency label.
```

To make the emergency label permanent set `EMERGENCY_LABEL_PERMANENT` to true. Doing this will cause the app to reapply the emergency label if it is removed.
To trigger the label (and therefore everything configured) set `TRIGGER_STRING` to the value you want the app to look for in PRs and PR comments.

Expand Down
112 changes: 105 additions & 7 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ const emergencyLabel = process.env.EMERGENCY_LABEL || 'emergency';
module.exports = (app) => {
//console.log("Yay! The app was loaded!");
app.on("pull_request.labeled", async (context) => {
if (context.payload.label.name == emergencyLabel && context.payload.pull_request.merged == false) {
let authorized = await isAuthorized(context.payload.sender.login, context.payload.organization.login, context.octokit)
if (context.payload.label.name == emergencyLabel
&& context.payload.pull_request.merged == false
&& authorized) {
// emergency label exists and pull request is not merged, so do stuff...
console.log(`${emergencyLabel} label detected`);

Expand Down Expand Up @@ -122,11 +125,25 @@ module.exports = (app) => {
} else {
return true;
}
}
} else if (context.payload.label.name == emergencyLabel
&& context.payload.pull_request.merged == false
&& ! authorized) {
await postUnauthorizedIssueComment(context)
// remove emergency label
await context.octokit.rest.issues.removeLabel({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
issue_number: context.payload.pull_request.number,
name: emergencyLabel
})
}
});

app.on("pull_request.unlabeled", async (context) => {
if (context.payload.label.name == emergencyLabel && process.env.EMERGENCY_LABEL_PERMANENT == 'true') {
if (context.payload.label.name == emergencyLabel
&& process.env.EMERGENCY_LABEL_PERMANENT == 'true'
&& ! context.payload.sender.login.endsWith("[bot]")) {

// emergencyLabel was removed and it should be permanent, so do stuff...
console.log(`Reaplying ${emergencyLabel} label to PR: ${context.payload.pull_request.html_url}`);

Expand Down Expand Up @@ -156,7 +173,10 @@ module.exports = (app) => {
});

app.on("issues.unlabeled", async (context) => {
if (context.payload.label.name == emergencyLabel && process.env.EMERGENCY_LABEL_PERMANENT == 'true') {
if (context.payload.label.name == emergencyLabel
&& process.env.EMERGENCY_LABEL_PERMANENT == 'true'
&& ! context.payload.sender.login.endsWith("[bot]")) {

// emergencyLabel was removed and it should be permanent, so do stuff...
console.log(`Reaplying ${emergencyLabel} label to PR: ${context.payload.issue.html_url}`);

Expand Down Expand Up @@ -186,7 +206,10 @@ module.exports = (app) => {
});

app.on("pull_request.opened", async (context) => {
if (context.payload.pull_request.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)) {
let authorized = await isAuthorized(context.payload.sender.login, context.payload.organization.login, context.octokit)
if (context.payload.pull_request.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)
&& authorized) {

// Found the trigger string, so add the emergency label to trigger the other stuff...
let errorsArray = [];
await context.octokit.rest.issues.addLabels({
Expand All @@ -209,11 +232,18 @@ module.exports = (app) => {
} else {
return true;
}
} else if (context.payload.pull_request.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)
&& ! authorized){
await postUnauthorizedIssueComment(context)
}
});

app.on("issue_comment.created", async (context) => {
if (context.payload.issue.pull_request && context.payload.comment.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)) {
let authorized = await isAuthorized(context.payload.sender.login, context.payload.organization.login, context.octokit)
if (context.payload.issue.pull_request
&& context.payload.comment.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)
&& authorized) {

// This is a comment on a PR and we found the trigger string, so add the emergency label to trigger the other stuff...
let errorsArray = [];
await context.octokit.rest.issues.addLabels({
Expand All @@ -236,6 +266,74 @@ module.exports = (app) => {
} else {
return true;
}
}
} else if (context.payload.issue.pull_request
&& context.payload.comment.body.toLocaleLowerCase().includes(process.env.TRIGGER_STRING)
&& ! authorized) {
await postUnauthorizedIssueComment(context)
}
});
};

async function isAuthorized(login, org, octokit) {
// check if process.env.AUTHORIZED_TEAM is defined
if (process.env.AUTHORIZED_TEAM == undefined || process.env.AUTHORIZED_TEAM == "") {
console.log("No authorized team specified. Skipping authorization check.")
return true;
}
// if login ends with [bot] then it's a bot and we don't need to check
if (login.endsWith("[bot]")) {
console.log("Bot detected. Skipping authorization check.")
return true;
}
console.log(`Checking if ${login} is a member of ${org}/${process.env.AUTHORIZED_TEAM} team`)
try {
let membership = await octokit.request(`GET /orgs/${org}/teams/${process.env.AUTHORIZED_TEAM}/memberships/${login}`, {
org: org,
team_slug: process.env.AUTHORIZED_TEAM,
username: login
})

if (membership.data.state == 'active') {
console.log( "Membership active")
return true;
} else {
console.log( "Membership not active")
return false;

}
} catch (error) {
if (error.status == 404) {
console.log("Membership not found")
return false;
} else {
console.log(`error: ${error}`);
console.log("Error checking membership. Check the ADMIN_OPS_ORG and ACTIONS_APPROVER_TEAM variables.")
throw new Error("Error checking membership");
}
}
}

async function postUnauthorizedIssueComment(context) {
// Comment on github issue that user is not authorized to apply the emergency label
let errorsArray = [];
let number = context.payload.issue ? context.payload.issue.number : context.payload.pull_request.number
let url = context.payload.issue ? context.payload.issue.html_url : context.payload.pull_request.html_url
await context.octokit.rest.issues.createComment({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
issue_number: number,
body: `@${context.payload.sender.login} is not authorized to apply the emergency label.`
}).then(response => {
console.log(`Commented on issue: ${url}`);
}).catch(error => {
console.log(`Error commenting on issue: ${error} to PR: ${url}`);
errorsArray.push(error);
});

if (errorsArray.length > 0) {
console.log(`Errors: ${errorsArray}`);
throw errorsArray;
} else {
return true;
}
}
4 changes: 4 additions & 0 deletions template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ Parameters:
Description: The string value in a PR or Issue title that triggers appling the emegency label
Default: emergency landing
Type: String
authorizedTeam:
Description: The team who's members are authorized to use this app
Type: String

Resources:
webhooks:
Expand Down Expand Up @@ -108,6 +111,7 @@ Resources:
SLACK_MESSAGE_FILE: !Ref slackMessageFile
EMERGENCY_LABEL_PERMANENT: !Ref emergencyLabelPermanent
TRIGGER_STRING: !Ref triggerString
AUTHORIZED_TEAM: !Ref authorizedTeam

Outputs:
WebhooksUrl:
Expand Down
Loading

0 comments on commit b5bf6bf

Please sign in to comment.