Files
anonymous_github/test/streamer-public-access.test.js
T

123 lines
6.1 KiB
JavaScript

const { expect } = require("chai");
const express = require("express");
const got = require("got");
const { Readable, PassThrough } = require("stream");
require("ts-node/register/transpile-only");
const config = require("../src/config").default;
const { registerGitHubToken, githubTokenForStreamer } = require("../src/core/github-token-context");
const File = require("../src/core/AnonymizedFile").default;
const GitHubStream = require("../src/core/source/GitHubStream").default;
const { AnonymizeTransformer } = require("../src/core/anonymize-utils");
const streamer = require("../src/streamer/route").default;
async function rejects(promise, message) {
try { await promise; } catch (error) { expect(error.message).to.equal(message); return; }
throw new Error("Expected rejection");
}
async function listen(app) {
return new Promise(resolve => {
const server = app.listen(0, "127.0.0.1", () => resolve(server));
});
}
describe("public repository streamer handoff", function () {
it("revalidates public access without exporting handles or owner credentials", async function () {
let valid = true;
registerGitHubToken("public-read:transport", {
quotaKey: "test", publicRepository: "owner/public",
renew: async () => { if (!valid) throw new Error("revoked"); return "private-owner-token"; },
});
expect(await githubTokenForStreamer("public-read:transport", "owner/public")).to.equal("");
expect(await githubTokenForStreamer("installation-token", "owner/private")).to.equal("installation-token");
await rejects(githubTokenForStreamer("public-read:transport", "other/repo"), "Public repository access context mismatch");
valid = false;
await rejects(githubTokenForStreamer("public-read:transport", "owner/public"), "revoked");
await rejects(githubTokenForStreamer("public-read:missing", "owner/public"), "Public repository access context expired");
});
it("rejects send when the access recheck fails before opening the streamer", async function () {
const endpoint = config.STREAMER_ENTRYPOINT;
config.STREAMER_ENTRYPOINT = "http://unused.test/";
const response = new PassThrough();
try {
registerGitHubToken("public-read:revoked-send", {
quotaKey: "test", publicRepository: "owner/public", renew: async () => { throw new Error("revoked"); },
});
const file = new File({ repository: {
options: { terms: [] }, model: { source: { repositoryName: "owner/public" } },
getToken: async () => "public-read:revoked-send",
generateAnonymizeTransformer: filePath => new AnonymizeTransformer({ terms: [], filePath }),
}, anonymizedPath: "README.md" });
file._file = { name: "README.md", path: "", sha: "sha", size: 25 };
await rejects(file.send(response), "revoked");
} finally {
config.STREAMER_ENTRYPOINT = endpoint;
response.destroy();
}
});
for (const [mode, commit] of [
["send", "abc"], ["anonymizedContent", "abc"], ["send", undefined], ["send", ""],
]) {
it(`serves a public README through ${mode} with commit ${JSON.stringify(commit)}`, async function () {
const previous = { endpoint: config.STREAMER_ENTRYPOINT, stream: got.stream, cache: GitHubStream.prototype.getFileContentCache };
const servers = [];
let payload;
const requests = [];
try {
registerGitHubToken("public-read:http-test", {
quotaKey: "test", publicRepository: "owner/public", renew: async () => "private-owner-token",
});
got.stream = (url, options) => {
if (!String(url).startsWith("https://github.com/")) return previous.stream(url, options);
requests.push({ url, options });
return Readable.from((async function* () {
for (const hook of options.hooks.beforeRequest) await hook();
yield Buffer.from("# README\nAlice wrote this.");
})());
};
GitHubStream.prototype.getFileContentCache = async function (path) {
return this.downloadWithFallback(await this.data.getToken(), "sha", path);
};
const upstream = express();
upstream.use(express.json());
upstream.use((req, _res, next) => { payload = req.body; next(); });
upstream.use("/api", streamer);
servers.push(await listen(upstream));
config.STREAMER_ENTRYPOINT = `http://127.0.0.1:${servers[0].address().port}/`;
const options = { terms: ["Alice"], image: true, link: true };
const repo = {
repoId: "test", options,
model: { source: { repositoryName: "owner/public", commit } },
getToken: async () => "public-read:http-test",
generateAnonymizeTransformer: path => new AnonymizeTransformer({ ...options, filePath: path }),
};
const file = new File({ repository: repo, anonymizedPath: "README.md" });
file._file = { name: "README.md", path: "", sha: "sha", size: 25 };
const api = express();
api.get("/file", async (_req, res) => {
try {
if (mode === "send") await file.send(res);
else (await file.anonymizedContent()).pipe(res);
} catch (error) { res.status(500).json({ error: error.message }); }
});
servers.push(await listen(api));
const response = await got(`http://127.0.0.1:${servers[1].address().port}/file`);
expect(response.body).to.equal("# README\nXXXX-1 wrote this.");
expect(payload.token).to.equal("");
expect(JSON.stringify(payload)).not.to.include("public-read:");
expect(JSON.stringify(payload)).not.to.include("private-owner-token");
expect(requests).to.have.length(1);
expect(requests[0].url).to.equal(`https://github.com/owner/public/raw/${commit || "HEAD"}/README.md`);
expect(requests[0].options.headers).not.to.have.property("authorization");
} finally {
got.stream = previous.stream;
GitHubStream.prototype.getFileContentCache = previous.cache;
config.STREAMER_ENTRYPOINT = previous.endpoint;
await Promise.all(servers.map(server => new Promise(resolve => server.close(resolve))));
}
});
}
});