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

Commit

Permalink
review changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Bharatkk-metron committed Aug 2, 2024
1 parent 0f04ed1 commit 9c2d878
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 198 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ STATUS=OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED
#createdInLast: optional
#To retrieve issues created during a time span before the current time (exclusive). Accepted units are 'y' for year, 'm' for month, 'w' for week and 'd' for day. If this parameter is set, createdAfter must not be set
# Example value: 1m2w (1 month 2 weeks)
CREATED_IN_LAST=10d # default value is 10 days
FINDINGS_INGEST_SINCE_DAYS=90 # default value is 90 days

# CODE_SMELL -> MAINTAINABILITY in API Version 2
# BUG -> RELIABILITY in API Version 2
Expand Down
4 changes: 2 additions & 2 deletions src/getStepStartStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export default function getStepStartStates(
): StepStartStates {
context.logger.warn(
{
enableFindingsIngestion: !!context.instance.config
.enableFindingsIngestion,
enableFindingsIngestion:
!!context.instance.config.enableFindingsIngestion,
},
'Findings ingestion flag',
);
Expand Down
5 changes: 0 additions & 5 deletions src/ingestionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { IntegrationIngestionConfigFieldMap } from '@jupiterone/integration-sdk-
import { INGESTION_SOURCE_IDS } from './steps/constants';

export const ingestionConfig: IntegrationIngestionConfigFieldMap = {
[INGESTION_SOURCE_IDS.ACCOUNT]: {
title: 'Account',
description: 'SonarQube Accounts',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.FINDINGS]: {
title: 'Users',
description: 'SonarQube Issues',
Expand Down
8 changes: 4 additions & 4 deletions src/instanceConfigFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
mask: false,
optional: true,
},
severities: {
findingsSeverities: {
type: 'string',
mask: false,
optional: true,
},
status: {
findingStatus: {
type: 'string',
mask: false,
optional: true,
},
createdInLast: {
findingsIngestSinceDays: {
type: 'string',
mask: false,
optional: true,
},
types: {
findingTypes: {
type: 'string',
mask: false,
optional: true,
Expand Down
7 changes: 3 additions & 4 deletions src/provider/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface Pagination {

export type PaginatedResponse<T extends string, U> = {
[Key in 'paging' | 'page']: Pagination;
} &
{
[K in T]: U[];
};
} & {
[K in T]: U[];
};
2 changes: 1 addition & 1 deletion src/steps/account/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function createAccountEntity(
_class: Entities.ACCOUNT._class,
id,
name,
vendor: 'SonarSource'
vendor: 'SonarSource',
},
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/steps/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const V1_SEVERITIES_VALUES = [
'BLOCKER',
];
export const V2_SEVERITIES_VALUES = ['LOW', 'MEDIUM', 'HIGH'];
export const DEFAULT_CREATED_IN_LAST = '10d';
export const DEFAULT_FINDING_INGEST_SINCE_DAYS = 90;

export const FINDING_STATUSES = {
OPEN: 'OPEN',
Expand Down
168 changes: 60 additions & 108 deletions src/steps/finding/__recordings__/fetch-findings_4198425312/recording.har

Large diffs are not rendered by default.

69 changes: 26 additions & 43 deletions src/steps/finding/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import {
} from '@jupiterone/integration-sdk-core';

import {
DEFAULT_CREATED_IN_LAST,
DEFAULT_FINDING_INGEST_SINCE_DAYS,
Entities,
FINDING_STATUSES,
FINDING_TYPES,
FINDINGS_SEVERITIES,
INGESTION_SOURCE_IDS,
Relationships,
Steps,
Expand All @@ -28,47 +25,28 @@ import {
import { APIVersion } from '../../provider/types/common';
import { createProjectEntityIdentifier } from '../project/converter';

function getSeverities(instanceConfig: SonarqubeIntegrationConfig) {
const { severities, apiVersion } = instanceConfig;
const severitiesSet = new Set(
severities?.split(',').map((severity) => FINDINGS_SEVERITIES[severity]),
);

// V2 -> 10.4 or above version
if (apiVersion == APIVersion.V1) {
return severities ? severities.split(',') : V1_SEVERITIES_VALUES;
}
return severitiesSet.size === 0
? V2_SEVERITIES_VALUES
: Array.from(severitiesSet);
}

function getFilterParams(
instanceConfig: SonarqubeIntegrationConfig,
): NodeJS.Dict<string | string[]> {
const { apiVersion, status, createdInLast, types } = instanceConfig;

let filterParams: NodeJS.Dict<string | string[]>;

if (apiVersion === APIVersion.V1) {
// V1 -> below 10.4 version
filterParams = {
status,
types,
};
} else {
const statusesSet = new Set(
status?.split(',').map((status) => FINDING_STATUSES[status]),
);
const typesSet = new Set(
types?.split(',').map((type) => FINDING_TYPES[type]),
);
filterParams = {
issueStatuses: Array.from(statusesSet).join(','),
impactSoftwareQualities: Array.from(typesSet).join(','),
};
const { apiVersion, findingStatus, findingsIngestSinceDays, findingTypes } =
instanceConfig;

const filterParams: NodeJS.Dict<string | string[]> = {};

const statusKey = apiVersion === APIVersion.V1 ? 'status' : 'issueStatuses';
const typesKey =
apiVersion === APIVersion.V1 ? 'types' : 'impactSoftwareQualities';

if (findingStatus) {
filterParams[statusKey] = findingStatus;
}
if (findingTypes) {
filterParams[typesKey] = findingTypes;
}
filterParams['createdInLast'] = createdInLast || DEFAULT_CREATED_IN_LAST;

filterParams['createdInLast'] =
`${findingsIngestSinceDays || DEFAULT_FINDING_INGEST_SINCE_DAYS}d`;

return filterParams;
}

Expand All @@ -79,7 +57,12 @@ export async function fetchFindings({
}: IntegrationStepExecutionContext<SonarqubeIntegrationConfig>) {
const client = createSonarqubeClient(instance.config, logger);

const severities = getSeverities(instance.config);
const severityList = instance.config.findingSeverities
? instance.config.findingSeverities
: instance.config.apiVersion === APIVersion.V1
? V1_SEVERITIES_VALUES
: V2_SEVERITIES_VALUES;

const filterParams = getFilterParams(instance.config);

await jobState.iterateEntities(
Expand All @@ -95,7 +78,7 @@ export async function fetchFindings({
// We need to further filter our API calls in order to minimize the chances
// we'll hit the 10,000 limit impose by the API. We're currently filtering
// by project and severity.
for (const severity of severities) {
for (const severity of severityList) {
if (instance.config.apiVersion == APIVersion.V1) {
filterParams['severities'] = severity;
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export interface SonarqubeIntegrationConfig extends IntegrationInstanceConfig {
* where the parameter {@param endpointVersion} determines the url to query
*/
apiVersion: APIVersion;
severities?: string;
status?: string;
createdInLast?: string;
types?: string;
findingSeverities?: string[];
findingStatus?: string[];
findingsIngestSinceDays?: number;
findingTypes?: string[];
}
43 changes: 20 additions & 23 deletions src/validateInvocation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ describe('#validateInvocation', () => {
},
});

const executionContext = createMockExecutionContext<
SonarqubeIntegrationConfig
>({
instanceConfig: {} as SonarqubeIntegrationConfig,
});
const executionContext =
createMockExecutionContext<SonarqubeIntegrationConfig>({
instanceConfig: {} as SonarqubeIntegrationConfig,
});

await expect(validateInvocation(executionContext)).rejects.toThrowError(
IntegrationValidationError,
Expand All @@ -57,15 +56,14 @@ describe('#validateInvocation', () => {
mutateEntry: mutations.unzipGzippedRecordingEntry,
});

const executionContext = createMockExecutionContext<
SonarqubeIntegrationConfig
>({
instanceConfig: {
baseUrl: process.env.BASE_URL || 'http://localhost:9000',
apiToken: 'INVALID',
apiVersion: APIVersion.V1,
},
});
const executionContext =
createMockExecutionContext<SonarqubeIntegrationConfig>({
instanceConfig: {
baseUrl: process.env.BASE_URL || 'http://localhost:9000',
apiToken: 'INVALID',
apiVersion: APIVersion.V1,
},
});

await expect(validateInvocation(executionContext)).rejects.toThrowError(
IntegrationValidationError,
Expand All @@ -87,15 +85,14 @@ describe('#validateInvocation', () => {
mutateEntry: mutations.unzipGzippedRecordingEntry,
});

const executionContext = createMockExecutionContext<
SonarqubeIntegrationConfig
>({
instanceConfig: {
baseUrl: 'http://example.com',
apiToken: process.env.API_TOKEN || 'string-value',
apiVersion: APIVersion.V1,
},
});
const executionContext =
createMockExecutionContext<SonarqubeIntegrationConfig>({
instanceConfig: {
baseUrl: 'http://example.com',
apiToken: process.env.API_TOKEN || 'string-value',
apiVersion: APIVersion.V1,
},
});

await expect(validateInvocation(executionContext)).rejects.toThrowError(
IntegrationValidationError,
Expand Down
87 changes: 87 additions & 0 deletions src/validateInvocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { createSonarqubeClient } from './provider/index';
import { SonarqubeIntegrationConfig } from './types';
import { SonarqubeSystemInfo } from './provider/types/v1';
import { APIVersion } from './provider/types/common';
import {
FINDING_STATUSES,
FINDING_TYPES,
FINDINGS_SEVERITIES,
} from './steps/constants';

function getApiVersion(systemInfo: SonarqubeSystemInfo): APIVersion {
const INITIAL_SONARQUBE_WITH_API_V2 = '10.4';
Expand All @@ -16,6 +21,24 @@ function getApiVersion(systemInfo: SonarqubeSystemInfo): APIVersion {
return shouldUseV1 ? APIVersion.V1 : APIVersion.V2;
}

function validSeverities(severities: string[]) {
return severities.every((severity) =>
['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'].includes(severity),
);
}

function validStatuses(statuses: string[]) {
return statuses.every((status) =>
['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'].includes(status),
);
}

function validTypes(types: string[]) {
return types.every((type) =>
['CODE_SMELL', 'BUG', 'VULNERABILITY'].includes(type),
);
}

export default async function validateInvocation({
instance,
logger,
Expand All @@ -32,6 +55,70 @@ export default async function validateInvocation({
config.baseUrl = config.baseUrl.slice(0, -1);
}

if (config.findingSeverities) {
const findingSeverities = (config.findingSeverities as unknown as string)
.split(',')
.map((s) => s.trim());
if (!validSeverities(findingSeverities)) {
throw new IntegrationValidationError(
'Invalid Finding severities. Valid severities are INFO, MINOR, MAJOR, CRITICAL, BLOCKER',
);
}

if (config.apiVersion == APIVersion.V1) {
config.findingSeverities = findingSeverities;
} else {
const findingSeveritiesSet = new Set(
config.findingSeverities.map(
(findingSeverity) => FINDINGS_SEVERITIES[findingSeverity],
),
);
config.findingSeverities = Array.from(findingSeveritiesSet);
}
}

if (config.findingStatus) {
const findingStatus = (config.findingStatus as unknown as string)
.split(',')
.map((s) => s.trim());
if (!validStatuses(findingStatus)) {
throw new IntegrationValidationError(
'Invalid Finding Status. Valid severities are OPEN, CONFIRMED, REOPENED, RESOLVED, CLOSED',
);
}

if (config.apiVersion == APIVersion.V1) {
config.findingSeverities = findingStatus;
} else {
const findingStatusSet = new Set(
config.findingStatus.map(
(findingStatus) => FINDING_STATUSES[findingStatus],
),
);
config.findingStatus = Array.from(findingStatusSet);
}
}

if (config.findingTypes) {
const findingTypes = (config.findingTypes as unknown as string)
.split(',')
.map((s) => s.trim());
if (!validTypes(findingTypes)) {
throw new IntegrationValidationError(
'Invalid vulnerability severities. Valid severities are CODE_SMELL,BUG,VULNERABILITY',
);
}

if (config.apiVersion == APIVersion.V1) {
config.findingTypes = findingTypes;
} else {
const findingStatusSet = new Set(
config.findingTypes.map((findingType) => FINDING_TYPES[findingType]),
);
config.findingTypes = Array.from(findingStatusSet);
}
}

const client = createSonarqubeClient(instance.config, logger);
try {
const systemInfo = await client.fetchSystemInfo();
Expand Down
5 changes: 3 additions & 2 deletions test/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ if (process.env.LOAD_ENV) {
const DEFAULT_API_TOKEN = 'dummy-sonarqube-api-token';
const DEFAULT_BASE_URL = 'http://127.0.0.1:9000';
const DEFAULT_API_VERSION = APIVersion.V2;
const DEFAULT_CREATED_IN_LAST = '1m';
const DEFAULT_CREATED_IN_LAST = 90;

export function buildStepTestConfigForStep(stepId: string): StepTestConfig {
const integrationConfig: SonarqubeIntegrationConfig = {
apiToken: process.env.API_TOKEN || DEFAULT_API_TOKEN,
baseUrl: process.env.BASE_URL || DEFAULT_BASE_URL,
// Using env var to determine version since we don't want to query the system info when running a test
apiVersion: (process.env.API_VERSION as APIVersion) || DEFAULT_API_VERSION,
createdInLast: process.env.CREATED_IN_LAST || DEFAULT_CREATED_IN_LAST
findingsIngestSinceDays:
process.env.CREATED_IN_LAST ? + process.env.CREATED_IN_LAST : DEFAULT_CREATED_IN_LAST,
};

return {
Expand Down

0 comments on commit 9c2d878

Please sign in to comment.