@dataroadinc/setup-auth
Version:
CLI tool and programmatic API for automated OAuth setup across cloud platforms
159 lines (158 loc) • 8.05 kB
JavaScript
import { SetupAuthError } from "../../../utils/error.js";
import { superJoin } from "../../../utils/string.js";
import { OrganizationsClient } from "@google-cloud/resource-manager";
import { backOff } from "exponential-backoff";
import { BACKOFF_OPTIONS, BaseGcpIamManager } from "./base-iam.js";
import { ORGANIZATION_PERMISSIONS, ORGANIZATION_ROLES, } from "./constants.js";
export class GcpOrganizationIamManager extends BaseGcpIamManager {
constructor(identity, organizationId, client) {
super(identity);
this.organizationId = organizationId;
this.organizationsClient = client;
}
async initializeSpecific() {
if (!this.organizationsClient) {
console.log("IAM Manager initializing its own OrganizationsClient...");
this.organizationsClient = new OrganizationsClient({
auth: await this.identity.getGaxAuthClient(),
});
}
}
setClient(client) {
this.organizationsClient = client;
}
formatResourceName(organizationId) {
return `organizations/${organizationId}`;
}
async testPermissions(permissions) {
await this.initialize();
if (!this.organizationsClient) {
throw new SetupAuthError("OrganizationsClient not initialized");
}
try {
const resource = this.formatResourceName(this.organizationId);
console.log(`Testing organization permissions on: ${resource}`);
console.log(`Permissions being tested: ${superJoin(permissions)}\n`);
const [response] = await backOff(() => this.organizationsClient.testIamPermissions({
resource,
permissions,
}), BACKOFF_OPTIONS);
const grantedPermissions = new Set(response.permissions || []);
console.log(`Granted organization permissions found: ${superJoin(Array.from(grantedPermissions))}\n`);
return grantedPermissions;
}
catch (error) {
console.error(`Error testing organization permissions on ${this.formatResourceName(this.organizationId)}:`, error);
throw new SetupAuthError(`Failed to test permissions for organization ${this.organizationId}.`, { cause: error instanceof Error ? error : new Error(String(error)) });
}
}
async getIamPolicy() {
await this.initialize();
if (!this.organizationsClient) {
throw new SetupAuthError("OrganizationsClient not initialized");
}
const [policy] = await backOff(() => this.organizationsClient.getIamPolicy({
resource: this.formatResourceName(this.organizationId),
}), BACKOFF_OPTIONS);
if (!policy) {
throw new SetupAuthError("Could not get organization IAM policy");
}
return {
version: policy.version ?? undefined,
bindings: policy.bindings?.map(binding => ({
role: binding.role || "",
members: binding.members || [],
})) || [],
etag: policy.etag?.toString() || undefined,
};
}
async setIamPolicy(policy) {
await this.initialize();
if (!this.organizationsClient) {
throw new SetupAuthError("OrganizationsClient not initialized");
}
await backOff(() => this.organizationsClient.setIamPolicy({
resource: this.formatResourceName(this.organizationId),
policy,
}), BACKOFF_OPTIONS);
}
async addRoles(userId, roles) {
const policy = await this.getIamPolicy();
const member = `user:${userId}`;
if (!policy.bindings) {
policy.bindings = [];
}
for (const role of roles) {
const existingBinding = policy.bindings.find((b) => b.role === role);
if (existingBinding) {
if (!existingBinding.members.includes(member)) {
existingBinding.members.push(member);
}
}
else {
policy.bindings.push({
role,
members: [member],
});
}
}
await this.setIamPolicy(policy);
}
async checkPermissions() {
await this.initialize();
const requiredPermissions = Object.values(ORGANIZATION_PERMISSIONS);
console.log(`Checking organization-specific permissions for user ${this.userEmail}...`);
try {
const grantedPermissions = await this.testPermissions(requiredPermissions);
const missingPermissions = requiredPermissions.filter(permission => !grantedPermissions.has(permission));
if (missingPermissions.length > 0) {
console.warn(`Missing organization permissions for ${this.userEmail}: ${missingPermissions.join(", ")}`);
}
else {
console.log(`User ${this.userEmail} has all required organization permissions.`);
}
return { missingPermissions };
}
catch (error) {
console.error(`Error during organization permission check for ${this.userEmail}:`, error);
if (error instanceof SetupAuthError)
throw error;
throw new SetupAuthError(`Failed to check permissions for organization ${this.organizationId}.`, { cause: error });
}
}
async ensurePermissions() {
await this.initialize();
const { missingPermissions } = await this.checkPermissions();
if (missingPermissions.length === 0) {
console.log("All required organization permissions (including projects.setIamPolicy) are already granted.");
return;
}
console.log(`Attempting to add org role ${ORGANIZATION_ROLES.OWNER} to potentially grant missing permissions...`);
try {
await this.addRoles(this.userEmail, [ORGANIZATION_ROLES.OWNER]);
}
catch (error) {
console.warn(`Failed to add organization role ${ORGANIZATION_ROLES.OWNER}:`, error);
throw new SetupAuthError(`Failed to add '${ORGANIZATION_ROLES.OWNER}' role at the organization level. ` +
`Cannot ensure required permissions. Original error: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : new Error(String(error)) });
}
console.log(`Re-checking for critical permission: ${ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY}`);
const grantedCheck = await this.testPermissions([
ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY,
]);
if (!grantedCheck.has(ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY)) {
console.error(`Critical permission ${ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY} is still missing for user ${this.userEmail} at the organization level even after attempting to add owner role.`);
throw new SetupAuthError(`User ${this.userEmail} lacks required permission '${ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY}' at the organization level (${this.organizationId}). ` +
"This permission is necessary to manage project roles. Please ensure the user has sufficient organization-level privileges (e.g., Org Admin, or a custom role with this permission).");
}
else {
console.log(`Successfully verified critical permission ${ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY} is present.`);
const { missingPermissions: finalMissing } = await this.checkPermissions();
if (finalMissing.length > 0 &&
!finalMissing.includes(ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY)) {
console.warn(`Note: While critical permission ${ORGANIZATION_PERMISSIONS.PROJECTS_SET_IAM_POLICY} is present, some other checked organization permissions are still missing: ${finalMissing.join(", ")}`);
}
}
console.log("Organization-level permission check passed.");
}
}