-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix GitHub events not verifying (#875)
* Ensure we verify the raw payload. * changelog * Tidy up types * Add test for GitHib * Mock out GitHub API to allow tests to pass. * Lint
- Loading branch information
Showing
8 changed files
with
200 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix GitHub events not working due to verification failures. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { E2ESetupTestTimeout, E2ETestEnv } from "./util/e2e-test"; | ||
import { describe, it, beforeEach, afterEach } from "@jest/globals"; | ||
import { createHmac, randomUUID } from "crypto"; | ||
import { GitHubRepoConnection, GitHubRepoConnectionState } from "../src/Connections"; | ||
import { MessageEventContent } from "matrix-bot-sdk"; | ||
import { getBridgeApi } from "./util/bridge-api"; | ||
import { Server, createServer } from "http"; | ||
|
||
describe('GitHub', () => { | ||
let testEnv: E2ETestEnv; | ||
let githubServer: Server; | ||
const webhooksPort = 9500 + E2ETestEnv.workerId; | ||
const githubPort = 9700 + E2ETestEnv.workerId; | ||
|
||
beforeEach(async () => { | ||
// Fake out enough of a GitHub API to get past startup. Later | ||
// tests might make more use of this. | ||
githubServer = createServer((req, res) => { | ||
if (req.method === 'GET' && req.url === '/api/v3/app') { | ||
res.writeHead(200, undefined, { "content-type": 'application/json'}); | ||
res.write(JSON.stringify({})); | ||
} else if (req.method === 'GET' && req.url === '/api/v3/app/installations?per_page=100&page=1') { | ||
res.writeHead(200, undefined, { "content-type": 'application/json'}); | ||
res.write(JSON.stringify([])); | ||
} else { | ||
console.log('Unknown request', req.method, req.url); | ||
res.writeHead(404); | ||
} | ||
res.end(); | ||
}).listen(githubPort); | ||
testEnv = await E2ETestEnv.createTestEnv({matrixLocalparts: ['user'], config: { | ||
github: { | ||
webhook: { | ||
secret: randomUUID(), | ||
}, | ||
// So we can mock out the URL | ||
enterpriseUrl: `http://localhost:${githubPort}`, | ||
auth: { | ||
privateKeyFile: 'replaced', | ||
id: '1234', | ||
} | ||
}, | ||
widgets: { | ||
publicUrl: `http://localhost:${webhooksPort}` | ||
}, | ||
listeners: [{ | ||
port: webhooksPort, | ||
bindAddress: '0.0.0.0', | ||
// Bind to the SAME listener to ensure we don't have conflicts. | ||
resources: ['webhooks', 'widgets'], | ||
}], | ||
}}); | ||
await testEnv.setUp(); | ||
}, E2ESetupTestTimeout); | ||
|
||
afterEach(() => { | ||
githubServer?.close(); | ||
return testEnv?.tearDown(); | ||
}); | ||
|
||
it.only('should be able to handle a GitHub event', async () => { | ||
const user = testEnv.getUser('user'); | ||
const bridgeApi = await getBridgeApi(testEnv.opts.config?.widgets?.publicUrl!, user); | ||
const testRoomId = await user.createRoom({ name: 'Test room', invite:[testEnv.botMxid] }); | ||
await user.setUserPowerLevel(testEnv.botMxid, testRoomId, 50); | ||
await user.waitForRoomJoin({sender: testEnv.botMxid, roomId: testRoomId }); | ||
// Now hack in a GitHub connection. | ||
await testEnv.app.appservice.botClient.sendStateEvent(testRoomId, GitHubRepoConnection.CanonicalEventType, "my-test", { | ||
org: 'my-org', | ||
repo: 'my-repo' | ||
} satisfies GitHubRepoConnectionState); | ||
|
||
// Wait for connection to be accepted. | ||
await new Promise<void>(r => { | ||
let interval: NodeJS.Timeout; | ||
interval = setInterval(() => { | ||
bridgeApi.getConnectionsForRoom(testRoomId).then(conns => { | ||
if (conns.length > 0) { | ||
clearInterval(interval); | ||
r(); | ||
} | ||
}) | ||
}, 500); | ||
}); | ||
|
||
const webhookNotice = user.waitForRoomEvent<MessageEventContent>({ | ||
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId | ||
}); | ||
|
||
const webhookPayload = JSON.stringify({ | ||
"action": "opened", | ||
"number": 1, | ||
"pull_request": { | ||
id: 1, | ||
"url": "https://api.github.com/repos/my-org/my-repo/pulls/1", | ||
"html_url": "https://github.com/my-org/my-repo/pulls/1", | ||
"number": 1, | ||
"state": "open", | ||
"locked": false, | ||
"title": "My test pull request", | ||
"user": { | ||
"login": "alice", | ||
}, | ||
}, | ||
repository: { | ||
id: 1, | ||
"html_url": "https://github.com/my-org/my-repo", | ||
name: 'my-repo', | ||
full_name: 'my-org/my-repo', | ||
owner: { | ||
login: 'my-org', | ||
} | ||
}, | ||
sender: { | ||
login: 'alice', | ||
} | ||
}); | ||
|
||
const hmac = createHmac('sha256', testEnv.opts.config?.github?.webhook.secret!); | ||
hmac.write(webhookPayload); | ||
hmac.end(); | ||
|
||
// Send a webhook | ||
const req = await fetch(`http://localhost:${webhooksPort}/`, { | ||
method: 'POST', | ||
headers: { | ||
'x-github-event': 'pull_request', | ||
'X-Hub-Signature-256': `sha256=${hmac.read().toString('hex')}`, | ||
'X-GitHub-Delivery': randomUUID(), | ||
'Content-Type': 'application/json' | ||
}, | ||
body: webhookPayload, | ||
}); | ||
expect(req.status).toBe(200); | ||
expect(await req.text()).toBe('OK'); | ||
|
||
// And await the notice. | ||
const { body } = (await webhookNotice).data.content; | ||
expect(body).toContain('**alice** opened a new PR'); | ||
expect(body).toContain('https://github.com/my-org/my-repo/pulls/1'); | ||
expect(body).toContain('My test pull request'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { MatrixClient } from "matrix-bot-sdk"; | ||
import { BridgeAPI } from "../../web/BridgeAPI"; | ||
import { WidgetApi } from "matrix-widget-api"; | ||
|
||
export async function getBridgeApi(publicUrl: string, user: MatrixClient) { | ||
return BridgeAPI.getBridgeAPI(publicUrl, { | ||
requestOpenIDConnectToken: () => { | ||
return user.getOpenIDConnectToken() | ||
}, | ||
} as unknown as WidgetApi, { | ||
getItem() { return null}, | ||
setItem() { }, | ||
} as unknown as Storage); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters