firebase-tools
Version:
Command-Line Interface for Firebase
126 lines (125 loc) • 5.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.addServiceAccountRoles = exports.firebaseRoles = void 0;
exports.getIamPolicy = getIamPolicy;
exports.setIamPolicy = setIamPolicy;
exports.addServiceAccountToRoles = addServiceAccountToRoles;
exports.serviceAccountHasRoles = serviceAccountHasRoles;
exports.removeServiceAccountRoles = removeServiceAccountRoles;
exports.getServiceAccountRoles = getServiceAccountRoles;
const api_1 = require("../api");
const apiv2_1 = require("../apiv2");
const error_1 = require("../error");
const utils = require("../utils");
const iam_1 = require("./iam");
const API_VERSION = "v1";
const apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.resourceManagerOrigin)(), apiVersion: API_VERSION });
exports.firebaseRoles = {
apiKeysViewer: "roles/serviceusage.apiKeysViewer",
authAdmin: "roles/firebaseauth.admin",
functionsDeveloper: "roles/cloudfunctions.developer",
hostingAdmin: "roles/firebasehosting.admin",
runViewer: "roles/run.viewer",
serviceUsageConsumer: "roles/serviceusage.serviceUsageConsumer",
};
async function getIamPolicy(projectIdOrNumber) {
const response = await apiClient.post(`/projects/${projectIdOrNumber}:getIamPolicy`);
return response.body;
}
async function setIamPolicy(projectIdOrNumber, newPolicy, updateMask = "") {
const response = await apiClient.post(`/projects/${projectIdOrNumber}:setIamPolicy`, {
policy: newPolicy,
updateMask: updateMask,
});
return response.body;
}
function isRetryableIamError(err) {
const status = (0, error_1.getErrStatus)(err);
if (status === 409) {
return true;
}
const msg = (0, error_1.getErrMsg)(err).toLowerCase();
const hasSa = msg.includes("service account") || msg.includes("serviceaccount");
if (status === 400 && hasSa && msg.includes("does not exist")) {
return true;
}
return false;
}
async function addServiceAccountToRoles(projectId, serviceAccountName, roles, skipAccountLookup = false) {
const { name: fullServiceAccountName } = skipAccountLookup
? { name: serviceAccountName }
: await (0, iam_1.getServiceAccount)(projectId, serviceAccountName);
const newMemberName = `serviceAccount:${fullServiceAccountName.split("/").pop()}`;
return await utils.retryWithBackoff(async () => {
const projectPolicy = await getIamPolicy(projectId);
projectPolicy.bindings = projectPolicy.bindings || [];
const requiredBindings = roles.map((role) => ({
role,
members: [newMemberName],
}));
(0, iam_1.mergeBindings)(projectPolicy, requiredBindings);
return await setIamPolicy(projectId, projectPolicy, "bindings");
}, {
retries: 7,
delay: 1000,
maxDelay: 5000,
retryPredicate: isRetryableIamError,
});
}
async function serviceAccountHasRoles(projectId, serviceAccountName, roles, skipAccountLookup = false) {
const [{ name: fullServiceAccountName }, projectPolicy] = await Promise.all([
skipAccountLookup
? Promise.resolve({ name: serviceAccountName })
: (0, iam_1.getServiceAccount)(projectId, serviceAccountName),
getIamPolicy(projectId),
]);
projectPolicy.bindings = projectPolicy.bindings || [];
const memberName = `serviceAccount:${fullServiceAccountName.split("/").pop()}`;
for (const roleName of roles) {
const binding = projectPolicy.bindings.find((b) => b.role === roleName);
if (!binding) {
return false;
}
if (!binding.members.includes(memberName)) {
return false;
}
}
return true;
}
async function removeServiceAccountRoles(projectId, serviceAccountEmail, rolesToRemove) {
const projectPolicy = await getIamPolicy(projectId);
const bindings = projectPolicy.bindings || [];
const memberName = `serviceAccount:${serviceAccountEmail}`;
let updated = false;
for (let i = bindings.length - 1; i >= 0; i--) {
const binding = bindings[i];
if (rolesToRemove.includes(binding.role)) {
const memberIndex = binding.members.indexOf(memberName);
if (memberIndex !== -1) {
binding.members.splice(memberIndex, 1);
updated = true;
if (binding.members.length === 0) {
bindings.splice(i, 1);
}
}
}
}
if (updated) {
projectPolicy.bindings = bindings;
return await setIamPolicy(projectId, projectPolicy, "bindings");
}
return projectPolicy;
}
async function getServiceAccountRoles(projectId, serviceAccountEmail) {
const projectPolicy = await getIamPolicy(projectId);
const memberName = `serviceAccount:${serviceAccountEmail}`;
const roles = [];
const bindings = projectPolicy.bindings || [];
for (const binding of bindings) {
if (binding.members.includes(memberName)) {
roles.push(binding.role);
}
}
return roles;
}
exports.addServiceAccountRoles = addServiceAccountToRoles;