mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-30 22:09:35 +02:00
fix: address streamer review edge cases
This commit is contained in:
1 parent
c9c729c2c5
commit
2e6b79b30d
5 files changed
+50
-16
No files matched your search
@@ -508,7 +508,7 @@ export default class AnonymizedFile {
|
|||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, res);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default class GitHubStream extends GitHubBase {
|
|||||||
const url = githubRawFileUrl(
|
const url = githubRawFileUrl(
|
||||||
this.data.organization,
|
this.data.organization,
|
||||||
this.data.repoName,
|
this.data.repoName,
|
||||||
this.data.commit,
|
this.data.commit || "HEAD",
|
||||||
filePath
|
filePath
|
||||||
);
|
);
|
||||||
logger.debug("downloading via raw URL (LFS)", { url });
|
logger.debug("downloading via raw URL (LFS)", { url });
|
||||||
|
|||||||
+10
-4
@@ -75,17 +75,23 @@ export async function streamAnonymizedZip(
|
|||||||
on(event: string, listener: (...args: unknown[]) => void): unknown;
|
on(event: string, listener: (...args: unknown[]) => void): unknown;
|
||||||
}
|
}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
const token = await opt.getToken();
|
||||||
|
if (!token) {
|
||||||
|
// The API already checked public access. Codeload serves public archives
|
||||||
|
// directly, avoiding the streamer's shared unauthenticated REST quota.
|
||||||
|
response = { url: `https://codeload.github.com/${encodeURIComponent(opt.organization)}/${encodeURIComponent(opt.repoName)}/zip/${encodeURIComponent(opt.commit || "HEAD")}` };
|
||||||
|
} else {
|
||||||
const source = new GitHubDownload({
|
const source = new GitHubDownload({
|
||||||
repoId: opt.repoId,
|
repoId: opt.repoId,
|
||||||
organization: opt.organization,
|
organization: opt.organization,
|
||||||
repoName: opt.repoName,
|
repoName: opt.repoName,
|
||||||
commit: opt.commit,
|
commit: opt.commit,
|
||||||
getToken: opt.getToken,
|
getToken: () => token,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response;
|
|
||||||
try {
|
|
||||||
response = await source.getZipUrl();
|
response = await source.getZipUrl();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const code = await classifyGitHubMissError(error, {
|
const code = await classifyGitHubMissError(error, {
|
||||||
organization: opt.organization,
|
organization: opt.organization,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const { expect } = require("chai");
|
const { expect } = require("chai");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const got = require("got");
|
const got = require("got");
|
||||||
const { Readable } = require("stream");
|
const { Readable, PassThrough } = require("stream");
|
||||||
require("ts-node/register/transpile-only");
|
require("ts-node/register/transpile-only");
|
||||||
const config = require("../src/config").default;
|
const config = require("../src/config").default;
|
||||||
const { registerGitHubToken, githubTokenForStreamer } = require("../src/core/github-token-context");
|
const { registerGitHubToken, githubTokenForStreamer } = require("../src/core/github-token-context");
|
||||||
@@ -36,8 +36,31 @@ describe("public repository streamer handoff", function () {
|
|||||||
await rejects(githubTokenForStreamer("public-read:missing", "owner/public"), "Public repository access context expired");
|
await rejects(githubTokenForStreamer("public-read:missing", "owner/public"), "Public repository access context expired");
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mode of ["send", "anonymizedContent"]) {
|
it("rejects send when the access recheck fails before opening the streamer", async function () {
|
||||||
it(`serves a public README through ${mode} and a separate HTTP 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 previous = { endpoint: config.STREAMER_ENTRYPOINT, stream: got.stream, cache: GitHubStream.prototype.getFileContentCache };
|
||||||
const servers = [];
|
const servers = [];
|
||||||
let payload;
|
let payload;
|
||||||
@@ -66,7 +89,7 @@ describe("public repository streamer handoff", function () {
|
|||||||
const options = { terms: ["Alice"], image: true, link: true };
|
const options = { terms: ["Alice"], image: true, link: true };
|
||||||
const repo = {
|
const repo = {
|
||||||
repoId: "test", options,
|
repoId: "test", options,
|
||||||
model: { source: { repositoryName: "owner/public", commit: "abc" } },
|
model: { source: { repositoryName: "owner/public", commit } },
|
||||||
getToken: async () => "public-read:http-test",
|
getToken: async () => "public-read:http-test",
|
||||||
generateAnonymizeTransformer: path => new AnonymizeTransformer({ ...options, filePath: path }),
|
generateAnonymizeTransformer: path => new AnonymizeTransformer({ ...options, filePath: path }),
|
||||||
};
|
};
|
||||||
@@ -86,7 +109,7 @@ describe("public repository streamer handoff", function () {
|
|||||||
expect(JSON.stringify(payload)).not.to.include("public-read:");
|
expect(JSON.stringify(payload)).not.to.include("public-read:");
|
||||||
expect(JSON.stringify(payload)).not.to.include("private-owner-token");
|
expect(JSON.stringify(payload)).not.to.include("private-owner-token");
|
||||||
expect(requests).to.have.length(1);
|
expect(requests).to.have.length(1);
|
||||||
expect(requests[0].url).to.equal("https://github.com/owner/public/raw/abc/README.md");
|
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");
|
expect(requests[0].options.headers).not.to.have.property("authorization");
|
||||||
} finally {
|
} finally {
|
||||||
got.stream = previous.stream;
|
got.stream = previous.stream;
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ async function fixture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("ZIP stream errors", function () {
|
describe("ZIP stream errors", function () {
|
||||||
it("finishes a valid ZIP with anonymized file content", async function () {
|
for (const commit of ["abc", undefined, ""]) {
|
||||||
|
it(`downloads a public ZIP without a REST lookup with commit ${JSON.stringify(commit)}`, async function () {
|
||||||
const input = await fixture();
|
const input = await fixture();
|
||||||
const previous = { stream: got.stream, zip: GitHubDownload.prototype.getZipUrl };
|
const previous = { stream: got.stream, zip: GitHubDownload.prototype.getZipUrl };
|
||||||
const response = new PassThrough();
|
const response = new PassThrough();
|
||||||
@@ -37,10 +38,13 @@ describe("ZIP stream errors", function () {
|
|||||||
const finished = once(parser, "finish");
|
const finished = once(parser, "finish");
|
||||||
response.pipe(parser);
|
response.pipe(parser);
|
||||||
try {
|
try {
|
||||||
GitHubDownload.prototype.getZipUrl = async () => ({ url: "https://example.test/archive.zip" });
|
GitHubDownload.prototype.getZipUrl = async () => { throw new Error("must not use the anonymous REST quota"); };
|
||||||
got.stream = () => Readable.from([input]);
|
got.stream = url => {
|
||||||
|
expect(url).to.equal(`https://codeload.github.com/owner/public/zip/${commit || "HEAD"}`);
|
||||||
|
return Readable.from([input]);
|
||||||
|
};
|
||||||
await streamAnonymizedZip({
|
await streamAnonymizedZip({
|
||||||
repoId: "test", organization: "owner", repoName: "public", commit: "abc",
|
repoId: "test", organization: "owner", repoName: "public", commit,
|
||||||
getToken: () => "", anonymizerOptions: { terms: ["private"], image: true, link: true },
|
getToken: () => "", anonymizerOptions: { terms: ["private"], image: true, link: true },
|
||||||
}, response);
|
}, response);
|
||||||
await finished;
|
await finished;
|
||||||
@@ -52,6 +56,7 @@ describe("ZIP stream errors", function () {
|
|||||||
response.destroy();
|
response.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it("aborts a download on an asynchronous anonymization timeout without crashing", async function () {
|
it("aborts a download on an asynchronous anonymization timeout without crashing", async function () {
|
||||||
const input = await fixture();
|
const input = await fixture();
|
||||||
@@ -73,7 +78,7 @@ describe("ZIP stream errors", function () {
|
|||||||
};
|
};
|
||||||
await streamAnonymizedZip({
|
await streamAnonymizedZip({
|
||||||
repoId: "test", organization: "owner", repoName: "public", commit: "abc",
|
repoId: "test", organization: "owner", repoName: "public", commit: "abc",
|
||||||
getToken: () => "", anonymizerOptions: { terms: ["private"], image: true, link: true },
|
getToken: () => "private-token", anonymizerOptions: { terms: ["private"], image: true, link: true },
|
||||||
}, response);
|
}, response);
|
||||||
await closed;
|
await closed;
|
||||||
await new Promise(resolve => setImmediate(resolve));
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
|||||||
Reference in new issue
Block a user