mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-20 22:51:18 +02:00
fix: repair private repository access from the edit page (#892)
* fix: repair private repository access from the edit page * fix: respect App rollout and reload renamed sources after repair * fix: gate access repair by status and update response tests
This commit is contained in:
1 parent
4a06aa9888
commit
1d326e7c32
9 files changed
+180
-23
No files matched your search
@@ -50,7 +50,7 @@ for (const type of ["repository", "pull-request"]) {
|
||||
expect(writes).to.have.length(0); expect(model.githubAccess.installationId).to.equal(100);
|
||||
});
|
||||
it("replaces only the binding while preserving the URL, source and snapshot", async function () {
|
||||
expect(await run()).to.deep.equal({ connection: "github-app" });
|
||||
expect(await run()).to.deep.equal({ connection: "github-app", fullName: "owner/private" });
|
||||
expect(writes).to.have.length(1);
|
||||
expect(writes[0].update).to.deep.equal({ $set: { githubAccess: selected.binding } });
|
||||
expect(writes[0].filter.githubAccess).to.deep.equal(model.githubAccess);
|
||||
|
||||
+43
-1
@@ -653,12 +653,54 @@ describeMongo("GitHub App credential and repository integration", function () {
|
||||
const body = { type: "repository", id: resource.repoId, connection: "github-app" };
|
||||
expect((await request("/github/connections/migrate", { ...body, preview: true })).status).to.equal(200);
|
||||
expect((await CachedRepos.findById(cached.id)).name).to.equal("other/old-public");
|
||||
expect((await request("/github/connections/migrate", body)).status).to.equal(200);
|
||||
const repaired = await request("/github/connections/migrate", body);
|
||||
expect(repaired.status).to.equal(200);
|
||||
expect(repaired.data.fullName).to.equal("other/public");
|
||||
expect((await Repos.findById(resource.id)).githubAccess.publicRead).to.equal(true);
|
||||
expect((await Repos.findById(resource.id)).source.repositoryName).to.equal("other/public");
|
||||
expect((await CachedRepos.findById(cached.id)).name).to.equal("other/public");
|
||||
});
|
||||
|
||||
for (const permission of ["granted", "missing", "replacement", "missing-commit"]) {
|
||||
it(`repairs a public binding after the source becomes private (${permission})`, async () => {
|
||||
const Repos = require("../src/core/model/anonymizedRepositories/anonymizedRepositories.model").default;
|
||||
await app.saveAppGrant(owner.id, data());
|
||||
const resource = await Repos.create({ repoId: "visibility-" + permission, owner: owner.id, status: "ready",
|
||||
source: { type: "GitHubStream", repositoryName: "owner/repo", commit: "pinned", branch: "main" },
|
||||
options: { terms: ["author"], update: false },
|
||||
githubAccess: { kind: "github-app", repositoryId: 42, publicRead: true, revision: "original" } });
|
||||
const before = resource.toObject();
|
||||
mock(url => {
|
||||
if (url.includes("/user/installations?")) return { installations: [{ id: 4, app_id: 123, account: { id: 10, login: "owner", type: "User" } }] };
|
||||
if (url.includes("/user/installations/4/repositories")) return { repositories: permission === "missing" ? [] : [
|
||||
{ id: permission === "replacement" ? 99 : 42, full_name: "owner/repo", private: true, visibility: "private" },
|
||||
] };
|
||||
if (url.endsWith("/repos/owner/repo") || url.endsWith("/repositories/42")) return { id: 42, full_name: "owner/repo", private: true, visibility: "private" };
|
||||
if (url.endsWith("/app/installations/4")) return { app_id: 123, permissions: { contents: "read", metadata: "read" } };
|
||||
if (url.endsWith("/access_tokens")) return { token: "ghs_private", expires_at: new Date(Date.now() + 3600000).toISOString() };
|
||||
if (url.endsWith("/repos/owner/repo/commits/pinned")) return permission === "missing-commit" ? { status: 404 } : { sha: "pinned" };
|
||||
throw new Error("Unexpected request " + url);
|
||||
});
|
||||
await rejects(app.boundAppToken(owner.id, resource.githubAccess, "owner/repo"), "github_app_access_required");
|
||||
const result = await request("/github/connections/migrate", { type: "repository", id: resource.repoId, connection: "github-app" });
|
||||
const after = await Repos.findById(resource.id);
|
||||
expect(after.repoId).to.equal(before.repoId);
|
||||
expect(after.toObject().source).to.deep.equal(before.source);
|
||||
expect(after.toObject().options).to.deep.equal(before.options);
|
||||
expect(after.status).to.equal("ready");
|
||||
if (permission === "granted") {
|
||||
expect(result.status).to.equal(200);
|
||||
expect(after.githubAccess.publicRead).not.to.equal(true);
|
||||
expect(after.githubAccess.installationId).to.equal(4);
|
||||
expect(after.githubAccess.repositoryId).to.equal(42);
|
||||
expect(await app.boundAppToken(owner.id, after.githubAccess, "owner/repo")).to.equal("ghs_private");
|
||||
} else {
|
||||
expect(result.status).to.equal(403);
|
||||
expect(after.toObject().githubAccess).to.deep.equal(before.githubAccess);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it("recovers a renamed private pull request through its stable repository ID", async () => {
|
||||
const PRs = require("../src/core/model/anonymizedPullRequests/anonymizedPullRequests.model").default;
|
||||
await app.saveAppGrant(owner.id, data());
|
||||
|
||||
@@ -903,6 +903,73 @@ describe("Vue 3 UI", function () {
|
||||
});
|
||||
}
|
||||
|
||||
for (const scenario of ["success", "failure", "renamed"]) {
|
||||
it(`repairs App access without replacing the source or snapshot (${scenario})`, async function () {
|
||||
const failed = scenario === "failure";
|
||||
const fullName = scenario === "renamed" ? "owner/renamed" : "owner/repo";
|
||||
let repaired = false;
|
||||
const available = value => () => repaired ? value : { __status: 403, body: { error: "github_app_access_required" } };
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/api/user": { username: "owner" },
|
||||
"/github/connections": { csrf: "repair-csrf", appEnabled: true, appConnected: true },
|
||||
"/api/repo/test": { connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", repositoryID: "saved-record", branch: "main", commit: "saved-sha" },
|
||||
options: { terms: ["author"], update: false, expirationMode: "never" } },
|
||||
"/api/repo/owner/repo/": available({ id: "saved-record", repo: "repo", defaultBranch: "main" }),
|
||||
"/api/repo/owner/repo/branches": available([{ name: "main", commit: "new-sha" }]),
|
||||
"/api/repo/owner/repo/readme": available("readme"),
|
||||
"/github/connections/migrate": async request => {
|
||||
expect(request.headers["X-CSRF-Token"]).to.equal("repair-csrf");
|
||||
expect(request.payload).to.deep.equal({ type: "repository", id: "test", connection: "github-app" });
|
||||
await delay(20);
|
||||
if (failed) return { __status: 403, body: { error: "github_app_access_required" } };
|
||||
repaired = true;
|
||||
return { connection: "github-app", fullName };
|
||||
},
|
||||
"/api/repo/owner/renamed/": { id: "saved-record", repo: "renamed", defaultBranch: "main" },
|
||||
"/api/repo/owner/renamed/branches": [{ name: "main", commit: "new-sha" }],
|
||||
"/api/repo/owner/renamed/readme": "readme",
|
||||
});
|
||||
await delay(60);
|
||||
const button = [...ui.window.document.querySelectorAll("button")].find(b => b.textContent === "Repair GitHub access");
|
||||
expect(button).not.to.equal(undefined);
|
||||
for (let node = button; node; node = node.parentElement) expect(ui.window.getComputedStyle(node).display).not.to.equal("none");
|
||||
button.click(); button.click();
|
||||
await delay(100);
|
||||
expect(ui.requests.filter(r => r.method === "POST")).to.have.length(1);
|
||||
expect(ui.window.document.querySelector("#commit").value).to.equal("saved-sha");
|
||||
expect(ui.requests.some(r => r.url.searchParams.get("reconnect") === "1")).to.equal(false);
|
||||
expect(ui.window.document.body.textContent).to.include(failed ? "Unable to repair GitHub access" : "GitHub access repaired");
|
||||
if (scenario === "renamed") {
|
||||
expect(ui.window.document.querySelector("#sourceUrl").value).to.equal("https://github.com/owner/renamed");
|
||||
const branches = ui.requests.find(r => r.url.pathname === "/api/repo/owner/renamed/branches");
|
||||
expect(branches.url.searchParams.get("anonymizedRepoId")).to.equal("test");
|
||||
expect(ui.window.document.querySelector("#branch").value).to.equal("main");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it("hides App repair controls when migrations are disabled", async function () {
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/github/connections": { appEnabled: false, appConnected: true },
|
||||
"/api/repo/test": { connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
|
||||
});
|
||||
expect([...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access" || b.textContent === "Manage access")).to.equal(false);
|
||||
});
|
||||
|
||||
for (const status of ["removed", "archived", "preparing", "queue", "download", "removing", "expiring", "ready", "error", "expired"]) {
|
||||
it(`gates App access repair for repository status ${status}`, async function () {
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/github/connections": { appEnabled: true, appConnected: true },
|
||||
"/api/repo/test": { status, connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
|
||||
});
|
||||
const shown = [...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access");
|
||||
expect(shown).to.equal(["ready", "error", "expired"].includes(status));
|
||||
});
|
||||
}
|
||||
|
||||
it("reconnects a recreated source without saving until the owner submits", async function () {
|
||||
const oldCommit = "deadbeef";
|
||||
const newCommit = "1a212f1deb74b123804ec90d098c0f843c30da5b";
|
||||
|
||||
Reference in new issue
Block a user