Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Commit

Permalink
Merge branch 'ubiquity:development' into dev-6
Browse files Browse the repository at this point in the history
  • Loading branch information
me505 authored Sep 26, 2023
2 parents 207a243 + e605a0f commit 887c3a2
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/conventional-commits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ubiquity/action-conventional-commits@v1.1.2
- uses: ubiquity/action-conventional-commits@master
2 changes: 1 addition & 1 deletion app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ default_events:
# - gollum
- issue_comment
- issues
# - label
- label
# - milestone
# - member
# - membership
Expand Down
54 changes: 53 additions & 1 deletion src/adapters/supabase/helpers/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export const upsertAccessControl = async (username: string, repository: string,
const { data: _data, error: _error } = await supabase.from("access").insert({
created_at: new Date().toUTCString(),
price_access: false,
time_access: false,
time_access: true,
multiplier_access: false,
priority_access: false,
...properties,
Expand Down Expand Up @@ -521,3 +521,55 @@ export const savePermit = async (permit: InsertPermit): Promise<Permit> => {
}
return getPermitFromDbData(data[0]);
};

export const saveLabelChange = async (username: string, repository: string, label_from: string, label_to: string, hasAccess: boolean) => {
const { supabase } = getAdapters();
const { data, error } = await supabase
.from("label_changes")
.insert({
username,
repository,
label_from,
label_to,
authorized: hasAccess || false,
created: new Date().toISOString(),
updated: new Date().toISOString(),
})
.select();
if (error) {
throw new Error(error.message);
}
if (!data || data.length === 0) {
throw new Error("No data returned");
}
return data[0];
};

export const getLabelChanges = async (repository: string, labels: string[]) => {
const { supabase } = getAdapters();
const logger = getLogger();

const { data, error } = await supabase.from("label_changes").select("*").in("label_to", labels).eq("repository", repository).eq("authorized", false);

logger.debug(`Getting label changes done, { data: ${JSON.stringify(data)}, error: ${JSON.stringify(error)} }`);

if (error) {
throw new Error(`Error getting label changes: ${error.message}`);
}

if (data.length === 0) {
return null;
}
return data[0];
};

export const _approveLabelChange = async (changeId: number) => {
const { supabase } = getAdapters();
const { error } = await supabase.from("label_changes").update({ authorized: true }).eq("id", changeId);

if (error) {
throw new Error(error.message);
}

return;
};
1 change: 1 addition & 0 deletions src/handlers/comment/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export enum IssueCommentCommands {

ALLOW = "/allow",
AUTOPAY = "/autopay",
AUTHORIZE = "/authorize",
}
2 changes: 1 addition & 1 deletion src/handlers/comment/handlers/assign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const assign = async (body: string) => {
commit: `@${payload.sender.login} ${deadLinePrefix} ${endTime.toUTCString()}`,
tips: `<h6>Tips:</h6>
<ul>
<li>Use <code>/wallet 0x0000...0000</code> if you want to update your registered payment wallet address @user.</li>
<li>Use <code>/wallet 0x0000...0000</code> if you want to update your registered payment wallet address @${payload.sender.login}.</li>
<li>Be sure to open a draft pull request as soon as possible to communicate updates on your progress.</li>
<li>Be sure to provide timely updates to us when requested, or you will be automatically unassigned from the bounty.</li>
<ul>`,
Expand Down
45 changes: 45 additions & 0 deletions src/handlers/comment/handlers/authorize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { _approveLabelChange, getLabelChanges } from "../../../adapters/supabase";
import { getBotContext, getLogger } from "../../../bindings";
import { getUserPermission } from "../../../helpers";
import { Payload } from "../../../types";
import { ErrorDiff } from "../../../utils/helpers";
import { bountyInfo } from "../../wildcard";

export const approveLabelChange = async () => {
const context = getBotContext();
const logger = getLogger();
const payload = context.payload as Payload;
const sender = payload.sender.login;

logger.info(`Received '/authorize' command from user: ${sender}`);

const { issue, repository } = payload;
if (!issue) {
logger.info(`Skipping '/authorize' because of no issue instance`);
return;
}

// check if sender is admin
// passing in context so we don't have to make another request to get the user
const permissionLevel = await getUserPermission(sender, context);

// if sender is not admin, return
if (permissionLevel !== "admin" && permissionLevel !== "billing_manager") {
logger.info(`User ${sender} is not an admin/billing_manager`);
return ErrorDiff(`You are not an admin/billing_manager and do not have the required permissions to access this function.`);
}

const issueDetailed = bountyInfo(issue);

if (!issueDetailed.priceLabel || !issueDetailed.priorityLabel || !issueDetailed.timelabel) {
logger.info(`Skipping... its not a bounty`);
return ErrorDiff(`No valid bounty label on this issue`);
}

// check for label altering here
const labelChanges = await getLabelChanges(repository.full_name, [issueDetailed.priceLabel, issueDetailed.priorityLabel, issueDetailed.timelabel]);

await _approveLabelChange(labelChanges.id);

return `Label change has been approved, permit can now be generated`;
};
8 changes: 8 additions & 0 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { listAvailableCommands } from "./help";
// import { payout } from "./payout";
import { unassign } from "./unassign";
import { registerWallet } from "./wallet";
import { approveLabelChange } from "./authorize";
import { setAccess } from "./allow";
import { ask } from "./ask";
import { multiplier } from "./multiplier";
Expand Down Expand Up @@ -48,6 +49,7 @@ export * from "./multiplier";
export * from "./query";
export * from "./comment-incentives";
export * from "./ask";
export * from "./authorize";

export interface RewardsResponse {
error: string | null;
Expand Down Expand Up @@ -312,6 +314,12 @@ export const userCommands = (): UserCommands[] => {
handler: setAccess,
callback: commandCallback,
},
{
id: IssueCommentCommands.AUTHORIZE,
description: `Approve a label change. Superuser only.`,
handler: approveLabelChange,
callback: commandCallback,
},
{
id: IssueCommentCommands.WALLET,
description: config.wallet.registerWalletWithVerification
Expand Down
21 changes: 14 additions & 7 deletions src/handlers/comment/handlers/query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getAllAccessLevels, getWalletInfo } from "../../../adapters/supabase";
import { getAllAccessLevels, getWalletInfo, upsertAccessControl } from "../../../adapters/supabase";
import { getBotContext, getLogger } from "../../../bindings";
import { Payload } from "../../../types";
import { ErrorDiff } from "../../../utils/helpers";

export const query = async (body: string) => {
const context = getBotContext();
Expand Down Expand Up @@ -28,12 +29,18 @@ export const query = async (body: string) => {
let data = await getAllAccessLevels(user, repo.full_name);
if (!data) {
logger.info(`Access info does not exist for @${user}`);
data = {
multiplier: false,
priority: false,
time: true,
price: false,
};
try {
await upsertAccessControl(user, repo.full_name, "time_access", true);
data = {
multiplier: false,
priority: false,
time: true,
price: false,
};
} catch (e) {
ErrorDiff(e);
return `Error upserting access info for @${user}`;
}
}
const walletInfo = await getWalletInfo(user, id?.toString());
if (!walletInfo?.address) {
Expand Down
30 changes: 30 additions & 0 deletions src/handlers/label/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { saveLabelChange } from "../../adapters/supabase";
import { getBotContext, getLogger } from "../../bindings";
import { hasLabelEditPermission } from "../../helpers";
import { Payload } from "../../types";

export const watchLabelChange = async () => {
const logger = getLogger();
const context = getBotContext();

const payload = context.payload as Payload;

const { repository, label, changes, sender } = payload;

const { full_name } = repository;

const previousLabel = changes?.name.from;
const currentLabel = label?.name;
const triggerUser = sender.login;

if (!previousLabel || !currentLabel) {
logger.debug("watchLabelChange: No label name change.. skipping");
return;
}

// check if user is authorized to make the change
const hasAccess = await hasLabelEditPermission(currentLabel, triggerUser, repository.full_name);

await saveLabelChange(triggerUser, full_name, previousLabel, currentLabel, hasAccess);
logger.debug("watchLabelChange: label name change saved to db");
};
13 changes: 12 additions & 1 deletion src/handlers/payout/action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BigNumber, ethers } from "ethers";
import { getPenalty, getWalletAddress, getWalletMultiplier, removePenalty } from "../../adapters/supabase";
import { getLabelChanges, getPenalty, getWalletAddress, getWalletMultiplier, removePenalty } from "../../adapters/supabase";
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import {
addLabelToIssue,
Expand Down Expand Up @@ -194,6 +194,17 @@ export const incentivesCalculation = async (): Promise<IncentivesCalculationResu
throw new Error(`Permit generation disabled because this issue didn't qualify as bounty.`);
}

// check for label altering here
const labelChanges = await getLabelChanges(repository.full_name, [issueDetailed.priceLabel, issueDetailed.priorityLabel, issueDetailed.timelabel]);

if (labelChanges) {
// if approved is still false, it means user was certainly not authorized for that edit
if (!labelChanges.approved) {
logger.info(`Skipping... label was changed by unauthorized user`);
throw new Error(`Permit generation disabled because label: "${labelChanges.label_to}" was modified by an unauthorized user`);
}
}

const assignees = issue?.assignees ?? [];
const assignee = assignees.length > 0 ? assignees[0] : undefined;
if (!assignee) {
Expand Down
6 changes: 6 additions & 0 deletions src/handlers/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { checkPullRequests } from "./assign/auto";
import { createDevPoolPR } from "./pull-request";
import { runOnPush, validateConfigChange } from "./push";
import { findDuplicateOne } from "./issue";
import { watchLabelChange } from "./label";

export const processors: Record<string, Handler> = {
[GithubEvent.ISSUES_OPENED]: {
Expand Down Expand Up @@ -70,6 +71,11 @@ export const processors: Record<string, Handler> = {
action: [validateConfigChange, runOnPush],
post: [nullHandler],
},
[GithubEvent.LABEL_EDITED]: {
pre: [nullHandler],
action: [watchLabelChange],
post: [nullHandler],
},
};

/**
Expand Down
28 changes: 28 additions & 0 deletions src/helpers/payout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import { Static } from "@sinclair/typebox";
import { DEFAULT_RPC_ENDPOINT } from "../configs";
import { PayoutConfigSchema } from "../types";
import { getUserPermission } from "./issue";
import { getBotContext, getLogger } from "../bindings";
import { getAccessLevel } from "../adapters/supabase";

// available tokens for payouts
const PAYMENT_TOKEN_PER_NETWORK: Record<string, { rpc: string; token: string }> = {
Expand Down Expand Up @@ -45,3 +48,28 @@ export const getPayoutConfigByNetworkId = (networkId: number): PayoutConfigParti
paymentToken: paymentToken.token,
};
};

export const hasLabelEditPermission = async (label: string, caller: string, repository: string) => {
const context = getBotContext();
const logger = getLogger();
const permissionLevel = await getUserPermission(caller, context);

// get text before :
const match = label.split(":");
if (match.length == 0) return false;
const label_type = match[0].toLowerCase();

if (permissionLevel !== "admin" && permissionLevel !== "billing_manager") {
// check permission
const accessible = await getAccessLevel(caller, repository, label_type);

if (accessible) {
return true;
}

logger.info(`@${caller} is not allowed to edit label ${label}`);
return false;
}

return true;
};
10 changes: 10 additions & 0 deletions src/types/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export enum GithubEvent {

// push event
PUSH_EVENT = "push",

// label
LABEL_EDITED = "label.edited",
}

export enum UserType {
Expand Down Expand Up @@ -257,6 +260,12 @@ export const AssignEventSchema = Type.Object({

export type AssignEvent = Static<typeof AssignEventSchema>;

const ChangesSchema = Type.Object({
name: Type.Object({
from: Type.String(),
}),
});

export const PayloadSchema = Type.Object({
action: Type.String(),
issue: Type.Optional(IssueSchema),
Expand All @@ -267,6 +276,7 @@ export const PayloadSchema = Type.Object({
organization: Type.Optional(OrganizationSchema),
installation: Type.Optional(InstallationSchema),
repositories_added: Type.Optional(Type.Array(RepositorySchema)),
changes: Type.Optional(ChangesSchema),
});

export type Payload = Static<typeof PayloadSchema>;
Expand Down
10 changes: 10 additions & 0 deletions supabase/migrations/20230924193504_label_changes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS label_changes (
id serial PRIMARY KEY,
created timestamptz NOT NULL,
updated timestamptz NOT NULL,
username text NOT NULL,
repository text NOT NULL,
label_from text NOT NULL,
label_to text NOT NULL,
authorized boolean NOT NULL
);
1 change: 1 addition & 0 deletions supabase/migrations/20230924193752_label_changes_rls.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE label_changes ENABLE ROW LEVEL SECURITY

0 comments on commit 887c3a2

Please sign in to comment.