Check with repo-permission before accessing secrets and variables (#175)

This commit is contained in:
Laura Yu
2023-03-07 13:21:59 -08:00
committed by GitHub
parent 710386eb33
commit 47250a4ac8
4 changed files with 160 additions and 92 deletions
@@ -5,6 +5,7 @@ import {isMapping, isString} from "@github/actions-workflow-parser";
import {Octokit} from "@octokit/rest"; import {Octokit} from "@octokit/rest";
import {RepositoryContext} from "../initializationOptions"; import {RepositoryContext} from "../initializationOptions";
import {TTLCache} from "../utils/cache"; import {TTLCache} from "../utils/cache";
import {getRepoPermission} from "../utils/repo-permission";
export async function getSecrets( export async function getSecrets(
workflowContext: WorkflowContext, workflowContext: WorkflowContext,
@@ -13,6 +14,13 @@ export async function getSecrets(
repo: RepositoryContext, repo: RepositoryContext,
defaultContext: DescriptionDictionary | undefined defaultContext: DescriptionDictionary | undefined
): Promise<DescriptionDictionary> { ): Promise<DescriptionDictionary> {
const permission = await getRepoPermission(octokit, cache, repo);
if (permission === "none") {
const secretsContext = defaultContext || new DescriptionDictionary();
secretsContext.complete = false;
return secretsContext;
}
let environmentName: string | undefined; let environmentName: string | undefined;
if (workflowContext?.job?.environment) { if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) { if (isString(workflowContext.job.environment)) {
@@ -30,7 +38,6 @@ export async function getSecrets(
} }
const secretsContext = defaultContext || new DescriptionDictionary(); const secretsContext = defaultContext || new DescriptionDictionary();
try {
const secrets = await getRemoteSecrets(octokit, cache, repo, environmentName); const secrets = await getRemoteSecrets(octokit, cache, repo, environmentName);
// Build combined map of secrets // Build combined map of secrets
@@ -73,11 +80,7 @@ export async function getSecrets(
Array.from(secretsMap.values()) Array.from(secretsMap.values())
.sort((a, b) => a.key.localeCompare(b.key)) .sort((a, b) => a.key.localeCompare(b.key))
.forEach(secret => secretsContext?.add(secret.key, secret.value, secret.description)); .forEach(secret => secretsContext?.add(secret.key, secret.value, secret.description));
} catch (e: any) {
if (e.status === 403 || e.status === 404) {
secretsContext.complete = false;
}
}
return secretsContext; return secretsContext;
} }
@@ -6,6 +6,7 @@ import {Pair} from "@github/actions-expressions/data/expressiondata";
import {RepositoryContext} from "../initializationOptions"; import {RepositoryContext} from "../initializationOptions";
import {StringData} from "@github/actions-expressions/data/index"; import {StringData} from "@github/actions-expressions/data/index";
import {TTLCache} from "../utils/cache"; import {TTLCache} from "../utils/cache";
import {getRepoPermission} from "../utils/repo-permission";
export async function getVariables( export async function getVariables(
workflowContext: WorkflowContext, workflowContext: WorkflowContext,
@@ -14,6 +15,13 @@ export async function getVariables(
repo: RepositoryContext, repo: RepositoryContext,
defaultContext: DescriptionDictionary | undefined defaultContext: DescriptionDictionary | undefined
): Promise<DescriptionDictionary | undefined> { ): Promise<DescriptionDictionary | undefined> {
const permission = await getRepoPermission(octokit, cache, repo);
if (permission === "none") {
const secretsContext = defaultContext || new DescriptionDictionary();
secretsContext.complete = false;
return secretsContext;
}
let environmentName: string | undefined; let environmentName: string | undefined;
if (workflowContext?.job?.environment) { if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) { if (isString(workflowContext.job.environment)) {
@@ -31,7 +39,6 @@ export async function getVariables(
} }
const variablesContext = defaultContext || new DescriptionDictionary(); const variablesContext = defaultContext || new DescriptionDictionary();
try {
const variables = await getRemoteVariables(octokit, cache, repo, environmentName); const variables = await getRemoteVariables(octokit, cache, repo, environmentName);
// Build combined map of variables // Build combined map of variables
@@ -74,11 +81,7 @@ export async function getVariables(
Array.from(variablesMap.values()) Array.from(variablesMap.values())
.sort((a, b) => a.key.localeCompare(b.key)) .sort((a, b) => a.key.localeCompare(b.key))
.forEach(variable => variablesContext?.add(variable.key, variable.value, variable.description)); .forEach(variable => variablesContext?.add(variable.key, variable.value, variable.description));
} catch (e: any) {
if (e.status === 403 || e.status === 404) {
variablesContext.complete = false;
}
}
return variablesContext; return variablesContext;
} }
@@ -0,0 +1,46 @@
import {error} from "@github/actions-languageservice/log";
import {Octokit} from "@octokit/rest";
import {RepositoryContext} from "../initializationOptions";
import {TTLCache} from "./cache";
import {getUsername} from "./username";
export type RepoPermission = "admin" | "write" | "read" | "none";
export async function getRepoPermission(
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext
): Promise<RepoPermission> {
const username = await getUsername(octokit, cache);
const permission = await cache.get(`${repo.owner}/${repo.name}/${username}/permission`, undefined, () =>
fetchRepoPermission(octokit, repo, username)
);
switch (permission) {
case "admin":
case "write":
case "read":
case "none":
return permission;
default:
error(`Unknown permission: ${permission}`);
return "none";
}
}
async function fetchRepoPermission(octokit: Octokit, repo: RepositoryContext, username: string): Promise<string> {
try {
const res = await octokit.request("GET /repos/{owner}/{repo}/collaborators/{username}/permission", {
owner: repo.owner,
repo: repo.name,
username: username
});
const permission = res.data?.permission;
return permission;
} catch (e: any) {
if (e.status === 404 || e.status === 403) {
return "none";
}
throw e;
}
}
+16
View File
@@ -0,0 +1,16 @@
import {Octokit} from "@octokit/rest";
import {TTLCache} from "./cache";
export async function getUsername(octokit: Octokit, cache: TTLCache): Promise<string> {
return await cache.get(`/username`, undefined, () => fetchUsername(octokit));
}
async function fetchUsername(octokit: Octokit): Promise<string> {
try {
const username = await octokit.request("GET /user").then(res => res.data.login);
return username;
} catch (e) {
console.log("Failure to retrieve username: ", e);
throw e;
}
}