Merge pull request #2020 from pikasTech/fix/agentrun-private-pr-auth
fix: 投影 Artificer 私有仓 GitHub 凭据
This commit is contained in:
@@ -2,11 +2,14 @@ import { afterEach, describe, expect, test } from "bun:test";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { resolveToken, type GitHubYamlAuthConfig } from "./gh/auth-and-safety";
|
||||
import { authRequired } from "./gh/client";
|
||||
import { resolvePositionalIssueReference, resolvePositionalPrReference } from "./gh/refs";
|
||||
import type { GitHubOptions, GitHubResolvedNumberReference } from "./gh/types";
|
||||
|
||||
const originalGhToken = process.env.GH_TOKEN;
|
||||
const originalGithubToken = process.env.GITHUB_TOKEN;
|
||||
const projectedEnv = "UNIDESK_TEST_GH_REPOSITORY_OVERRIDE";
|
||||
const originalProjectedToken = process.env[projectedEnv];
|
||||
const temporaryRoots: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
@@ -14,6 +17,8 @@ afterEach(() => {
|
||||
else process.env.GH_TOKEN = originalGhToken;
|
||||
if (originalGithubToken === undefined) delete process.env.GITHUB_TOKEN;
|
||||
else process.env.GITHUB_TOKEN = originalGithubToken;
|
||||
if (originalProjectedToken === undefined) delete process.env[projectedEnv];
|
||||
else process.env[projectedEnv] = originalProjectedToken;
|
||||
while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop()!, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
@@ -84,6 +89,67 @@ describe("GitHub repository token overrides", () => {
|
||||
expect(selected.probe).toMatchObject({ source: "yaml-token-source", scope: "repository-override", yamlSourcePriority: "before-env" });
|
||||
});
|
||||
|
||||
test("uses the YAML-declared projected environment for repository overrides", () => {
|
||||
process.env.GH_TOKEN = "global-environment-token";
|
||||
process.env[projectedEnv] = "projected-repository-token";
|
||||
const authConfig = testAuthConfig();
|
||||
authConfig.repositoryOverrides[0] = {
|
||||
...authConfig.repositoryOverrides[0],
|
||||
sourceRef: "missing-token.txt",
|
||||
projectedEnv,
|
||||
};
|
||||
|
||||
const selected = resolveToken("pikainc/selfmedia", false, authConfig);
|
||||
|
||||
expect(selected.token).toBe("projected-repository-token");
|
||||
expect(selected.probe).toMatchObject({
|
||||
source: "yaml-token-projection",
|
||||
scope: "repository-override",
|
||||
sourceExists: false,
|
||||
sourceKeyPresent: false,
|
||||
projectedEnv,
|
||||
projectedEnvPresent: true,
|
||||
valuesPrinted: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("fails closed when a declared repository override is missing", () => {
|
||||
process.env.GH_TOKEN = "global-environment-token";
|
||||
delete process.env[projectedEnv];
|
||||
const authConfig = testAuthConfig();
|
||||
authConfig.repositoryOverrides[0] = {
|
||||
...authConfig.repositoryOverrides[0],
|
||||
sourceRef: "missing-token.txt",
|
||||
projectedEnv,
|
||||
};
|
||||
|
||||
const selected = resolveToken("pikainc/selfmedia", true, authConfig);
|
||||
const failure = authRequired("pikainc/selfmedia", "pr view", selected.probe);
|
||||
|
||||
expect(selected.token).toBeNull();
|
||||
expect(selected.probe).toMatchObject({
|
||||
present: false,
|
||||
scope: "repository-override",
|
||||
sourceExists: false,
|
||||
sourceKeyPresent: false,
|
||||
projectedEnv,
|
||||
projectedEnvPresent: false,
|
||||
ghFallbackAttempted: false,
|
||||
valuesPrinted: false,
|
||||
});
|
||||
expect(failure).toMatchObject({
|
||||
ok: false,
|
||||
degradedReason: "missing-token",
|
||||
runnerDisposition: "infra-blocked",
|
||||
token: {
|
||||
scope: "repository-override",
|
||||
projectedEnvPresent: false,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
});
|
||||
expect(JSON.stringify(failure)).not.toContain("global-environment-token");
|
||||
});
|
||||
|
||||
test("matches repository overrides without case sensitivity", () => {
|
||||
delete process.env.GH_TOKEN;
|
||||
delete process.env.GITHUB_TOKEN;
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface GitHubYamlTokenSourceConfig {
|
||||
sourceRef: string;
|
||||
sourceKey: string;
|
||||
format: "env" | "raw-token";
|
||||
projectedEnv?: string;
|
||||
scope: "repository-override" | "global";
|
||||
configRef: string;
|
||||
repository?: string;
|
||||
@@ -88,13 +89,16 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf
|
||||
const sourcePath = resolveYamlSourcePath(sourceRoot, config.sourceRef);
|
||||
const sourceExists = existsSync(sourcePath);
|
||||
const values = sourceExists ? parseYamlTokenSource(readFileSync(sourcePath, "utf8"), config) : {};
|
||||
const token = values[config.sourceKey] ?? null;
|
||||
const sourceToken = values[config.sourceKey] ?? null;
|
||||
const projectedToken = config.projectedEnv === undefined ? null : process.env[config.projectedEnv] ?? null;
|
||||
const token = sourceToken ?? projectedToken;
|
||||
const present = token !== null && token.length > 0;
|
||||
const projectedEnvPresent = projectedToken !== null && projectedToken.length > 0;
|
||||
return {
|
||||
token: present ? token : null,
|
||||
probe: {
|
||||
present,
|
||||
source: present ? "yaml-token-source" : null,
|
||||
source: sourceToken !== null ? "yaml-token-source" : projectedEnvPresent ? "yaml-token-projection" : null,
|
||||
scope: config.scope,
|
||||
ghFallbackAttempted: false,
|
||||
yamlSourceAttempted: true,
|
||||
@@ -104,7 +108,8 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf
|
||||
sourceRef: config.sourceRef,
|
||||
sourceKey: config.sourceKey,
|
||||
sourceExists,
|
||||
sourceKeyPresent: present,
|
||||
sourceKeyPresent: sourceToken !== null && sourceToken.length > 0,
|
||||
...(config.projectedEnv === undefined ? {} : { projectedEnv: config.projectedEnv, projectedEnvPresent }),
|
||||
tokenFingerprint: present ? fingerprintToken(token) : null,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
@@ -132,6 +137,7 @@ export function ghAuthToken(): string | null {
|
||||
export function resolveToken(repo: string, allowGhFallback: boolean, authConfig?: GitHubYamlAuthConfig): { token: string | null; probe: GitHubTokenProbe } {
|
||||
const yamlToken = tokenFromYamlSource(repo, authConfig);
|
||||
if (yamlToken.probe.yamlSourcePriority === "before-env" && yamlToken.probe.present) return yamlToken;
|
||||
if (yamlToken.probe.scope === "repository-override" && !yamlToken.probe.present) return yamlToken;
|
||||
const envProbe = tokenFromEnvironment();
|
||||
if (envProbe.present) {
|
||||
const token = envProbe.source === "GH_TOKEN" ? process.env.GH_TOKEN ?? null : process.env.GITHUB_TOKEN ?? null;
|
||||
@@ -179,6 +185,7 @@ function readGitHubYamlAuthConfig(): GitHubYamlAuthConfig {
|
||||
sourceRef: stringField(override, "sourceRef", configRef),
|
||||
sourceKey: envKeyField(override, "sourceKey", configRef),
|
||||
format: tokenSourceFormatField(override, "format", configRef),
|
||||
...(override.projectedEnv === undefined ? {} : { projectedEnv: envKeyField(override, "projectedEnv", configRef) }),
|
||||
scope: "repository-override" as const,
|
||||
configRef,
|
||||
};
|
||||
|
||||
@@ -388,6 +388,8 @@ function renderAuthStatus(result: GitHubCommandResult): string {
|
||||
`priority=${ghText(token.yamlSourcePriority)}`,
|
||||
`sourceRef=${ghText(token.sourceRef)}`,
|
||||
`key=${ghText(token.sourceKey)}`,
|
||||
`projectedEnv=${ghText(token.projectedEnv)}`,
|
||||
`projectedEnvPresent=${ghText(token.projectedEnvPresent)}`,
|
||||
`fingerprint=${ghText(token.tokenFingerprint)}`,
|
||||
`ghFallback=${ghText(token.ghFallbackAttempted)}`,
|
||||
].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" ");
|
||||
|
||||
@@ -410,7 +410,7 @@ export type GitHubCommandFailure = GitHubCommandResult & { ok: false };
|
||||
|
||||
export interface GitHubTokenProbe {
|
||||
present: boolean;
|
||||
source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "gh-auth-token" | null;
|
||||
source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "yaml-token-projection" | "gh-auth-token" | null;
|
||||
scope?: "repository-override" | "global";
|
||||
ghFallbackAttempted: boolean;
|
||||
ghBinaryFound?: boolean;
|
||||
@@ -423,6 +423,8 @@ export interface GitHubTokenProbe {
|
||||
sourceKey?: string;
|
||||
sourceExists?: boolean;
|
||||
sourceKeyPresent?: boolean;
|
||||
projectedEnv?: string;
|
||||
projectedEnvPresent?: boolean;
|
||||
tokenFingerprint?: string | null;
|
||||
valuesPrinted?: false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user