Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

INT-9750: change permission errors to warnings #620

Merged
merged 4 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { IntegrationExecutionContext } from '@jupiterone/integration-sdk-core';
import {
IntegrationExecutionContext,
IntegrationLogger,
} from '@jupiterone/integration-sdk-core';
import { ServiceUsageClient } from './steps/service-usage/client';
import { IntegrationConfig, SerializedIntegrationConfig } from './types';
import { deserializeIntegrationConfig } from './utils/integrationConfig';
import { google } from 'googleapis';
import { handleApiClientError } from './google-cloud/client';
import { ServiceUsageListFilter } from './google-cloud/types';

export async function executeTestRequest(config: IntegrationConfig) {
const googleClient = new ServiceUsageClient({ config });
export async function executeTestRequest(
config: IntegrationConfig,
logger: IntegrationLogger,
) {
try {
const client = google.serviceusage({ version: 'v1', retry: false });
const googleClient = new ServiceUsageClient({ config }, logger);
const auth = await googleClient.getAuthenticatedServiceClient();

return googleClient.collectEnabledServices();
await client.services.list({
parent: `projects/${config.serviceAccountKeyConfig.project_id}`,
pageSize: 200,
auth: auth,
filter: ServiceUsageListFilter.ENABLED,
});
} catch (err) {
throw handleApiClientError(err);
}
}

export async function validateInvocation(
context: IntegrationExecutionContext<SerializedIntegrationConfig>,
) {
const { instance } = context;
const { instance, logger } = context;
const { config: serializedIntegrationConfig } = instance;
const config = (context.instance.config = deserializeIntegrationConfig(
serializedIntegrationConfig,
));

await executeTestRequest(config);
await executeTestRequest(config, logger);
}
5 changes: 4 additions & 1 deletion src/getStepStartStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,10 @@ async function getStepStartStatesUsingServiceEnablements(params: {
let enabledServiceNames: string[];
let serviceAccountProjectEnabledServiceNames: string[];
try {
const enabledServiceData = await enablement.getEnabledServiceNames(config);
const enabledServiceData = await enablement.getEnabledServiceNames(
config,
logger,
);
enabledServiceNames = enabledServiceData.intersectedEnabledServices ?? [];
serviceAccountProjectEnabledServiceNames =
enabledServiceData.mainProjectEnabledServices ?? [];
Expand Down
24 changes: 17 additions & 7 deletions src/google-cloud/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
IntegrationLogger,
IntegrationProviderAPIError,
IntegrationProviderAuthorizationError,
} from '@jupiterone/integration-sdk-core';
Expand All @@ -13,6 +14,7 @@ import {
} from '../../test/recording';
import { parseServiceAccountKeyFile } from '../utils/parseServiceAccountKeyFile';
import { Client } from './client';
import { getMockLogger } from '../../test/helpers/getMockLogger';

describe('#getAuthenticatedServiceClient', () => {
let googleAuthSpy: jest.SpyInstance<
Expand Down Expand Up @@ -53,7 +55,9 @@ describe('#getAuthenticatedServiceClient', () => {

googleAuthSpy.mockReturnValueOnce(mockGoogleAuthClient);

const client = new Client({ config: instanceConfig });
const logger = getMockLogger<IntegrationLogger>();

const client = new Client({ config: instanceConfig }, logger);

const auth = await client.getAuthenticatedServiceClient();
const auth2 = await client.getAuthenticatedServiceClient();
Expand Down Expand Up @@ -94,9 +98,11 @@ describe('withErrorHandling', () => {
let client;
let onRetry;

const logger = getMockLogger<IntegrationLogger>();

beforeEach(() => {
onRetry = jest.fn();
client = new Client({ config, onRetry: onRetry });
client = new Client({ config, onRetry: onRetry }, logger);
});

[IntegrationProviderAuthorizationError, IntegrationProviderAPIError].forEach(
Expand Down Expand Up @@ -181,14 +187,16 @@ describe('withErrorHandling', () => {
});

describe('Client', () => {
const logger = getMockLogger<IntegrationLogger>();

test('should set projectId to the config projectId if provided', () => {
const configProjectId = 'projectId';
const serviceAccountProjectId = 'serviceAccountProjectId';
const config = {
projectId: configProjectId,
serviceAccountKeyConfig: { project_id: serviceAccountProjectId },
} as unknown as IntegrationConfig;
expect(new Client({ config }).projectId).toBe(configProjectId);
expect(new Client({ config }, logger).projectId).toBe(configProjectId);
});

test('should set projectId to the service account projectId if the configprojectId is not provided', () => {
Expand All @@ -198,7 +206,9 @@ describe('Client', () => {
projectId: configProjectId,
serviceAccountKeyConfig: { project_id: serviceAccountProjectId },
} as unknown as IntegrationConfig;
expect(new Client({ config }).projectId).toBe(serviceAccountProjectId);
expect(new Client({ config }, logger).projectId).toBe(
serviceAccountProjectId,
);
});

test('should set projectId to the projectId override option reguardless on if the service account projectId and config projectIds are provided or not', () => {
Expand All @@ -209,9 +219,9 @@ describe('Client', () => {
projectId: configProjectId,
serviceAccountKeyConfig: { project_id: serviceAccountProjectId },
} as unknown as IntegrationConfig;
expect(new Client({ config, projectId: overrideProjectId }).projectId).toBe(
overrideProjectId,
);
expect(
new Client({ config, projectId: overrideProjectId }, logger).projectId,
).toBe(overrideProjectId);
});
});

Expand Down
39 changes: 33 additions & 6 deletions src/google-cloud/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
IntegrationError,
IntegrationLogger,
IntegrationProviderAPIError,
IntegrationProviderAuthorizationError,
IntegrationWarnEventName,
} from '@jupiterone/integration-sdk-core';
import { retry } from '@lifeomic/attempt';
import { GaxiosError, GaxiosResponse } from 'gaxios';
Expand Down Expand Up @@ -44,12 +46,16 @@ export class Client {
readonly projectId: string;
readonly organizationId?: string;
readonly folderId?: string;
readonly logger: IntegrationLogger;

private credentials: CredentialBody;
private auth: BaseExternalAccountClient;
private readonly onRetry?: (err: any) => void;

constructor({ config, projectId, organizationId, onRetry }: ClientOptions) {
constructor(
{ config, projectId, organizationId, onRetry }: ClientOptions,
logger: IntegrationLogger,
) {
this.projectId =
projectId ||
config.projectId ||
Expand All @@ -61,6 +67,7 @@ export class Client {
};
this.folderId = config.folderId;
this.onRetry = onRetry;
this.logger = logger;
}

private async getClient(): Promise<BaseExternalAccountClient> {
Expand Down Expand Up @@ -91,19 +98,39 @@ export class Client {
callback: (data: T) => Promise<void>,
) {
return this.forEachPage(async (nextPageToken) => {
const result = await this.withErrorHandling(() => fn(nextPageToken));
await callback(result.data);
try {
const result = await this.withErrorHandling(() => fn(nextPageToken));
await callback(result.data);

return result;
return result;
} catch (err) {
if (err.status === 403) {
this.logger.warn(
{ err },
`Step failed due to missing permission. Requires additional permission`,
);

this.logger.publishWarnEvent({
name: IntegrationWarnEventName.MissingPermission,
description: `Received authorization error when attempting to call ${err.endpoint}. Please review permissions in the integration documentation.`,
});
return;
}

throw err;
}
});
}

async forEachPage<T>(
cb: (nextToken: string | undefined) => Promise<PageableGaxiosResponse<T>>,
cb: (
nextToken: string | undefined,
) => Promise<PageableGaxiosResponse<T> | undefined>,
): Promise<any> {
let nextToken: string | undefined;
do {
const response = await cb(nextToken);
if (!response) return;
nextToken = response.data.nextPageToken
? response.data.nextPageToken
: undefined;
Expand Down Expand Up @@ -151,7 +178,7 @@ export class Client {
/**
* Codes unknown error into JupiterOne errors
*/
function handleApiClientError(error: GaxiosError) {
export function handleApiClientError(error: GaxiosError) {
// If the error was already handled, forward it on
if (error instanceof IntegrationError) {
return error;
Expand Down
9 changes: 6 additions & 3 deletions src/steps/access-context-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export async function fetchAccessPolicies(
const {
jobState,
instance: { config },
logger,
} = context;
const client = new AccessContextManagerClient({ config });
const client = new AccessContextManagerClient({ config }, logger);
await client.iterateAccessPolicies(async (accessPolicy) => {
await jobState.addEntity(createAccessPolicyEntity(accessPolicy));
});
Expand All @@ -75,8 +76,9 @@ export async function fetchAccessLevels(
const {
jobState,
instance: { config },
logger,
} = context;
const client = new AccessContextManagerClient({ config });
const client = new AccessContextManagerClient({ config }, logger);

await jobState.iterateEntities(
{
Expand Down Expand Up @@ -333,8 +335,9 @@ export async function fetchServicePerimeters(
const {
jobState,
instance: { config },
logger,
} = context;
const client = new AccessContextManagerClient({ config });
const client = new AccessContextManagerClient({ config }, logger);

await jobState.iterateEntities(
{
Expand Down
8 changes: 5 additions & 3 deletions src/steps/api-gateway/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ export async function fetchApiGatewayApis(
const {
jobState,
instance: { config },
logger,
} = context;

const client = new ApiGatewayClient({ config });
const client = new ApiGatewayClient({ config }, logger);

await client.iterateApis(async (api) => {
const apiId = api.name?.split('/')[5];
Expand All @@ -78,7 +79,7 @@ export async function fetchApiGatewayApiConfigs(
logger,
} = context;

const client = new ApiGatewayClient({ config });
const client = new ApiGatewayClient({ config }, logger);

await jobState.iterateEntities(
{
Expand Down Expand Up @@ -151,9 +152,10 @@ export async function fetchApiGatewayGateways(
const {
jobState,
instance: { config },
logger,
} = context;

const client = new ApiGatewayClient({ config });
const client = new ApiGatewayClient({ config }, logger);

await client.iterateGateways(async (gateway) => {
const gatewayId = gateway.name?.split('/')[5];
Expand Down
8 changes: 4 additions & 4 deletions src/steps/app-engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function fetchAppEngineApplication(
instance: { config },
logger,
} = context;
const client = new AppEngineClient({ config });
const client = new AppEngineClient({ config }, logger);
const { projectId } = client;

let application: appengine_v1.Schema$Application | undefined;
Expand Down Expand Up @@ -223,7 +223,7 @@ export async function fetchAppEngineServices(
return;
}

const client = new AppEngineClient({ config });
const client = new AppEngineClient({ config }, logger);
const { projectId } = client;

await withAppEngineErrorHandling(logger, projectId, async () => {
Expand Down Expand Up @@ -251,7 +251,7 @@ export async function fetchAppEngineServiceVersions(
logger,
} = context;

const client = new AppEngineClient({ config });
const client = new AppEngineClient({ config }, logger);
const { projectId } = client;

await jobState.iterateEntities(
Expand Down Expand Up @@ -342,7 +342,7 @@ export async function fetchAppEngineVersionInstances(
logger,
} = context;

const client = new AppEngineClient({ config });
const client = new AppEngineClient({ config }, logger);
const { projectId } = client;

await jobState.iterateEntities(
Expand Down
7 changes: 4 additions & 3 deletions src/steps/big-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export async function fetchBigQueryDatasets(
const {
jobState,
instance: { config },
logger,
} = context;
const client = new BigQueryClient({ config });
const client = new BigQueryClient({ config }, logger);

try {
await client.iterateBigQueryDatasets(async (dataset) => {
Expand Down Expand Up @@ -140,7 +141,7 @@ export async function fetchBigQueryModels(
instance: { config },
logger,
} = context;
const client = new BigQueryClient({ config });
const client = new BigQueryClient({ config }, logger);

await jobState.iterateEntities(
{
Expand Down Expand Up @@ -183,7 +184,7 @@ export async function fetchBigQueryTables(
logger,
instance: { config },
} = context;
const client = new BigQueryClient({ config });
const client = new BigQueryClient({ config }, logger);

await jobState.iterateEntities(
{
Expand Down
Loading