diff --git a/src/Commands/Integration.ts b/src/Commands/Integration.ts deleted file mode 100644 index a925fab3..00000000 --- a/src/Commands/Integration.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { container } from '../Config'; -import { Bus, RabbitMQBusOptions } from '../Bus'; -import { - ConnectivityStatus, - IntegrationClient, - IntegrationPingTracer, - IntegrationOptions -} from '../Integrations'; -import { Helpers, logger } from '../Utils'; -import { StartupManager } from '../StartupScripts'; -import { - RequestProjectsHandler, - RegisterIssueHandler, - IntegrationConnected, - NetworkTestHandler -} from '../Handlers'; -import { Arguments, Argv, CommandModule } from 'yargs'; -import Timer = NodeJS.Timer; - -let timer: Timer; - -export class Integration implements CommandModule { - private static SERVICE_NAME = 'bright-integration'; - public readonly command = 'integration [options]'; - public readonly describe = 'Starts an on-prem integration.'; - - public builder(argv: Argv): Argv { - return argv - .option('token', { - alias: 't', - describe: 'Bright API-key', - requiresArg: true, - demandOption: true - }) - .option('type', { - choices: ['jira'], - requiresArg: true, - default: 'jira', - describe: 'Integration service type' - }) - .option('access-key', { - describe: 'Integration service key', - requiresArg: true, - demandOption: true - }) - .option('base-url', { - default: 'http://localhost:8080', - demandOption: true, - describe: 'The base URL to the Jira instance API' - }) - .option('timeout', { - number: true, - requiresArg: true, - default: 10000, - describe: - 'Time to wait for a server to send response headers (and start the response body) before aborting the request.' - }) - .option('user', { - alias: 'u', - describe: - 'Use username for Jira Server or email for Jira on Atlassian cloud.', - requiresArg: true, - demandOption: true - }) - .option('password', { - alias: 'p', - describe: - 'Use password for Jira Server or API token for Jira on Atlassian cloud.', - requiresArg: true, - demandOption: true - }) - .option('daemon', { - requiresArg: false, - alias: 'd', - describe: 'Run integration in daemon mode' - }) - .option('remove-daemon', { - requiresArg: false, - alias: ['rm', 'remove'], - describe: 'Stop and remove integration daemon' - }) - .option('run', { - requiresArg: false, - hidden: true - }) - .conflicts({ daemon: 'remove-daemon' }) - .middleware((args: Arguments) => { - container - .register(IntegrationOptions, { - useValue: { - // TODO: (victor.polyakov@brightsec.com) Write correct type checking - timeout: +(args.timeout as number), - apiKey: args.password as string, - user: args.user as string, - baseUrl: args.baseUrl as string, - insecure: args.insecure as boolean, - proxyUrl: (args.proxyInternal ?? args.proxy) as string - } - }) - .register(RabbitMQBusOptions, { - useValue: { - exchange: 'EventBus', - clientQueue: `integration:${args.accessKey}`, - connectTimeout: 10000, - url: args.bus as string, - proxyUrl: (args.proxyExternal ?? args.proxy) as string, - credentials: { - username: 'bot', - password: args.token as string - } - } - }); - }); - } - - public async handler(args: Arguments): Promise { - const bus: Bus = container.resolve(Bus); - const startupManager: StartupManager = container.resolve(StartupManager); - const pingTracers: IntegrationPingTracer[] = - container.resolveAll(IntegrationClient); - const pingTracer = pingTracers.find((p) => p.type === args.type); - - if (!pingTracer) { - logger.error(`Unsupported integration: ${args.type}`); - process.exit(1); - } - - const dispose: () => Promise = async (): Promise => { - clearInterval(timer); - await notify(ConnectivityStatus.DISCONNECTED); - await bus.destroy(); - }; - - if (args.remove) { - await startupManager.uninstall(Integration.SERVICE_NAME); - logger.log( - 'The Repeater daemon process (SERVICE: %s) was stopped and deleted successfully', - Integration.SERVICE_NAME - ); - process.exit(0); - } - - if (args.daemon) { - const { command, args: execArgs } = Helpers.getExecArgs({ - escape: false, - include: ['--run'], - exclude: ['--daemon', '-d'] - }); - - await startupManager.install({ - command, - args: execArgs, - name: Integration.SERVICE_NAME, - displayName: 'Bright Integration' - }); - - logger.log( - 'A Integration daemon process was initiated successfully (SERVICE: %s)', - Integration.SERVICE_NAME - ); - - process.exit(0); - } - - const onError = (e: Error) => { - clearInterval(timer); - logger.error(`Error during "integration": ${e.message}`); - process.exit(1); - }; - - const stop: () => Promise = async (): Promise => { - await dispose(); - process.exit(0); - }; - - process.on('SIGTERM', stop).on('SIGINT', stop).on('SIGHUP', stop); - - if (args.run) { - await startupManager.run(() => stop()); - } - - const notify = (connectivity: ConnectivityStatus) => - bus?.publish( - new IntegrationConnected(args.accessKey as string, connectivity) - ); - - const updateConnectivity = async (): Promise => - notify(await pingTracer.ping()); - - try { - await bus.init(); - - await bus.subscribe(RegisterIssueHandler); - await bus.subscribe(RequestProjectsHandler); - await bus.subscribe(NetworkTestHandler); - - timer = setInterval(() => updateConnectivity(), 10000); - - await updateConnectivity(); - } catch (e) { - await notify(ConnectivityStatus.DISCONNECTED); - onError(e); - } - } -} diff --git a/src/Commands/index.ts b/src/Commands/index.ts index 1661c74b..02b3e5d8 100644 --- a/src/Commands/index.ts +++ b/src/Commands/index.ts @@ -6,4 +6,3 @@ export { StopScan } from './StopScan'; export { PollingScanStatus } from './PollingScanStatus'; export { RunRepeater } from './RunRepeater'; export { Configure } from './Configure'; -export { Integration } from './Integration'; diff --git a/src/Config/container.ts b/src/Config/container.ts index 2f155ee3..3251efd4 100644 --- a/src/Config/container.ts +++ b/src/Config/container.ts @@ -41,7 +41,6 @@ import { ParserFactory, RestArchives } from '../Archive'; -import { IntegrationClient, JiraIntegrationClient } from '../Integrations'; import { ConfigReader } from './ConfigReader'; import { DefaultConfigReader } from './DefaultConfigReader'; import { CliInfo } from './CliInfo'; @@ -197,11 +196,6 @@ container }, { lifecycle: Lifecycle.Singleton } ) - .register( - IntegrationClient, - { useClass: JiraIntegrationClient }, - { lifecycle: Lifecycle.Singleton } - ) .register( Platform, { useClass: ReadlinePlatform }, diff --git a/src/Handlers/Events/IntegrationConnected.ts b/src/Handlers/Events/IntegrationConnected.ts deleted file mode 100644 index 9b7b30a7..00000000 --- a/src/Handlers/Events/IntegrationConnected.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Event } from '../../Bus'; - -export class IntegrationConnected implements Event { - constructor( - public readonly accessKey: string, - public readonly connectivity: 'connected' | 'disconnected' - ) {} -} diff --git a/src/Handlers/Events/RegisterIssue.ts b/src/Handlers/Events/RegisterIssue.ts deleted file mode 100644 index 3420fd42..00000000 --- a/src/Handlers/Events/RegisterIssue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IntegrationType, JiraIssue } from '../../Integrations'; -import { Event } from '../../Bus'; - -export class RegisterIssue implements Event { - constructor( - public readonly type: IntegrationType, - public readonly issue: JiraIssue - ) {} -} diff --git a/src/Handlers/Events/RequestProjects.ts b/src/Handlers/Events/RequestProjects.ts deleted file mode 100644 index cf88efdf..00000000 --- a/src/Handlers/Events/RequestProjects.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IntegrationType } from '../../Integrations'; -import { Event } from '../../Bus'; - -export class RequestProjects implements Event { - constructor(public readonly type: IntegrationType) {} -} diff --git a/src/Handlers/Events/index.ts b/src/Handlers/Events/index.ts index 8dce1150..48c32f8f 100644 --- a/src/Handlers/Events/index.ts +++ b/src/Handlers/Events/index.ts @@ -4,7 +4,4 @@ export * from './RepeaterRegistering'; export * from './RepeaterStatusUpdated'; export * from './RegisterScripts'; export * from './RepeaterRegistered'; -export * from './IntegrationConnected'; -export * from './RegisterIssue'; -export * from './RequestProjects'; export * from './NetworkTest'; diff --git a/src/Handlers/RegisterIssueHandler.ts b/src/Handlers/RegisterIssueHandler.ts deleted file mode 100644 index c82ba65c..00000000 --- a/src/Handlers/RegisterIssueHandler.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { bind, Handler } from '../Bus'; -import { RegisterIssue } from './Events'; -import { IntegrationClient, Ticket } from '../Integrations'; -import { injectable, injectAll } from 'tsyringe'; - -@injectable() -@bind(RegisterIssue) -export class RegisterIssueHandler implements Handler { - constructor( - @injectAll(IntegrationClient) - private readonly integrations: IntegrationClient[] - ) {} - - public handle({ issue, type }: RegisterIssue): Promise { - const integration = this.integrations.find((x) => x.type === type); - - if (!integration) { - throw new Error(`Unsupported integration "${type}"`); - } - - return integration.createTicket(issue); - } -} diff --git a/src/Handlers/RequestProjectsHandler.ts b/src/Handlers/RequestProjectsHandler.ts deleted file mode 100644 index 812adeb2..00000000 --- a/src/Handlers/RequestProjectsHandler.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { bind, Handler } from '../Bus'; -import { RequestProjects } from './Events'; -import { IntegrationClient, Project, Ticket } from '../Integrations'; -import { injectable, injectAll } from 'tsyringe'; - -@injectable() -@bind(RequestProjects) -export class RequestProjectsHandler - implements Handler -{ - constructor( - @injectAll(IntegrationClient) - private readonly integrations: IntegrationClient[] - ) {} - - public handle({ type }: RequestProjects): Promise { - const integration = this.integrations.find((x) => x.type === type); - - if (!integration) { - throw new Error(`Unsupported integration "${type}"`); - } - - return integration.getProjects(); - } -} diff --git a/src/Handlers/index.ts b/src/Handlers/index.ts index b33a0a9e..043e5945 100644 --- a/src/Handlers/index.ts +++ b/src/Handlers/index.ts @@ -1,6 +1,4 @@ export * from './Events'; export * from './SendRequestHandler'; export * from './RegisterScriptsHandler'; -export * from './RequestProjectsHandler'; -export * from './RegisterIssueHandler'; export * from './NetworkTestHandler'; diff --git a/src/Integrations/ConnectivityStatus.ts b/src/Integrations/ConnectivityStatus.ts deleted file mode 100644 index 8488d6ef..00000000 --- a/src/Integrations/ConnectivityStatus.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ConnectivityStatus { - CONNECTED = 'connected', - DISCONNECTED = 'disconnected' -} diff --git a/src/Integrations/IntegrationClient.ts b/src/Integrations/IntegrationClient.ts deleted file mode 100644 index 5b6f3fe8..00000000 --- a/src/Integrations/IntegrationClient.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ConnectivityStatus } from './ConnectivityStatus'; -import { IntegrationType } from './IntegrationType'; -import { Project } from './Project'; -import { Ticket } from './Ticket'; - -export interface IntegrationPingTracer { - type: IntegrationType; - - ping(): Promise; -} - -export interface IntegrationClient - extends IntegrationPingTracer { - getProjects(): Promise; - - createTicket(ticket: TTicket): Promise; -} - -export const IntegrationClient: unique symbol = Symbol('IntegrationClient'); diff --git a/src/Integrations/IntegrationOptions.ts b/src/Integrations/IntegrationOptions.ts deleted file mode 100644 index 8973ef50..00000000 --- a/src/Integrations/IntegrationOptions.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface IntegrationOptions { - readonly timeout: number; - readonly apiKey?: string; - readonly user?: string; - readonly baseUrl?: string; - readonly proxyUrl?: string; - readonly insecure?: boolean; -} - -export const IntegrationOptions: unique symbol = Symbol('IntegrationOptions'); diff --git a/src/Integrations/IntegrationType.ts b/src/Integrations/IntegrationType.ts deleted file mode 100644 index d9ef3371..00000000 --- a/src/Integrations/IntegrationType.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum IntegrationType { - JIRA = 'jira' -} diff --git a/src/Integrations/JiraClient.ts b/src/Integrations/JiraClient.ts deleted file mode 100644 index caa01e5d..00000000 --- a/src/Integrations/JiraClient.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Project } from './Project'; -import { Ticket } from './Ticket'; - -export interface IssueType { - readonly name: 'Task'; -} - -export interface JiraProject extends Project { - readonly self: string; - readonly key: string; - readonly name: string; -} - -export interface Fields { - readonly project: Omit; - readonly summary: string; - readonly description: string; - readonly issuetype: IssueType; -} - -export interface JiraIssue extends Ticket { - readonly fields: Fields; -} diff --git a/src/Integrations/JiraIntegrationClient.spec.ts b/src/Integrations/JiraIntegrationClient.spec.ts deleted file mode 100644 index 65a27a2e..00000000 --- a/src/Integrations/JiraIntegrationClient.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import 'reflect-metadata'; -import { JiraIntegrationClient } from './JiraIntegrationClient'; -import { IntegrationOptions } from './IntegrationOptions'; -import { ProxyFactory } from '../Utils'; -import { JiraIssue } from './JiraClient'; -import { ConnectivityStatus } from './ConnectivityStatus'; -import { instance, mock, reset } from 'ts-mockito'; -import nock from 'nock'; - -describe('JiraIntegrationClient', () => { - const proxyFactoryMock = mock(); - const integrationOptions: IntegrationOptions = { - timeout: 10000, - apiKey: 'testKey', - user: 'testUser', - baseUrl: 'http://example.com' - }; - - let jiraIntegrationClient!: JiraIntegrationClient; - - beforeAll(() => { - nock.disableNetConnect(); - nock.enableNetConnect('127.0.0.1'); - }); - - beforeEach(() => { - if (!nock.isActive()) { - nock.activate(); - } - - jiraIntegrationClient = new JiraIntegrationClient( - instance(proxyFactoryMock), - integrationOptions - ); - }); - - afterEach(() => { - reset(proxyFactoryMock); - nock.cleanAll(); - nock.restore(); - }); - - afterAll(() => nock.enableNetConnect()); - - describe('createTicket', () => { - it('should create a ticket', async () => { - const issue = { fields: { summary: 'Test issue' } } as JiraIssue; - - nock(integrationOptions.baseUrl) - .post('/rest/api/2/issue', JSON.stringify(issue)) - .reply( - 200, - {}, - { - 'content-type': 'application/json' - } - ); - - await jiraIntegrationClient.createTicket(issue); - - expect(nock.isDone()).toBeTruthy(); - }); - }); - - describe('getProjects', () => { - it('should return a list of projects', async () => { - const projects = [{ id: '1', name: 'Test project' }]; - - nock(integrationOptions.baseUrl) - .get('/rest/api/2/project') - .reply(200, projects, { - 'content-type': 'application/json' - }); - - const result = await jiraIntegrationClient.getProjects(); - - expect(result).toEqual(projects); - }); - }); - - describe('ping', () => { - it(`should return ${ConnectivityStatus.CONNECTED} if the ping is successful`, async () => { - nock(integrationOptions.baseUrl) - .get('/rest/api/2/project') - .reply(200, [], { - 'content-type': 'application/json' - }); - - const result = await jiraIntegrationClient.ping(); - - expect(result).toBe(ConnectivityStatus.CONNECTED); - }); - - it(`should return ${ConnectivityStatus.DISCONNECTED} if the ping fails`, async () => { - nock(integrationOptions.baseUrl) - .get('/rest/api/2/project') - .replyWithError('Test error'); - - const result = await jiraIntegrationClient.ping(); - - expect(result).toBe(ConnectivityStatus.DISCONNECTED); - }); - }); -}); diff --git a/src/Integrations/JiraIntegrationClient.ts b/src/Integrations/JiraIntegrationClient.ts deleted file mode 100644 index 96488383..00000000 --- a/src/Integrations/JiraIntegrationClient.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { JiraIssue, JiraProject } from './JiraClient'; -import { ConnectivityStatus } from './ConnectivityStatus'; -import { logger, ProxyFactory } from '../Utils'; -import { IntegrationClient } from './IntegrationClient'; -import { IntegrationType } from './IntegrationType'; -import { IntegrationOptions } from './IntegrationOptions'; -import { inject, injectable } from 'tsyringe'; -import axios, { Axios } from 'axios'; -import http from 'node:http'; -import https from 'node:https'; - -@injectable() -export class JiraIntegrationClient implements IntegrationClient { - private readonly client: Axios; - - get type(): IntegrationType { - return IntegrationType.JIRA; - } - - constructor( - @inject(ProxyFactory) private readonly proxyFactory: ProxyFactory, - @inject(IntegrationOptions) private readonly options: IntegrationOptions - ) { - const { - httpAgent = new http.Agent(), - httpsAgent = new https.Agent({ - rejectUnauthorized: !this.options.insecure - }) - } = this.options.proxyUrl - ? this.proxyFactory.createProxy({ - proxyUrl: this.options.proxyUrl, - rejectUnauthorized: !this.options.insecure - }) - : {}; - - this.client = axios.create({ - httpAgent, - httpsAgent, - baseURL: this.options.baseUrl, - timeout: this.options.timeout, - responseType: 'json', - headers: { - authorization: `Basic ${this.createToken( - this.options.user, - this.options.apiKey - )}` - } - }); - } - - public async createTicket(issue: JiraIssue): Promise { - await this.client.post('/rest/api/2/issue', issue); - } - - public async getProjects(): Promise { - const res = await this.client.get('/rest/api/2/project'); - - return res.data; - } - - public async ping(): Promise { - try { - await this.getProjects(); - - return ConnectivityStatus.CONNECTED; - } catch (err) { - logger.error(`Failed connect to the Jira integration, %s`, err.message); - - return ConnectivityStatus.DISCONNECTED; - } - } - - private createToken(user: string, apiKey: string): string { - return Buffer.from(`${user}:${apiKey}`).toString('base64'); - } -} diff --git a/src/Integrations/Project.ts b/src/Integrations/Project.ts deleted file mode 100644 index ae3bc103..00000000 --- a/src/Integrations/Project.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface Project {} diff --git a/src/Integrations/Ticket.ts b/src/Integrations/Ticket.ts deleted file mode 100644 index 1bdb39d0..00000000 --- a/src/Integrations/Ticket.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const Ticket: unique symbol = Symbol('Ticket'); - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface Ticket {} diff --git a/src/Integrations/index.ts b/src/Integrations/index.ts deleted file mode 100644 index 7082602b..00000000 --- a/src/Integrations/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './JiraIntegrationClient'; -export * from './JiraClient'; -export * from './Ticket'; -export * from './ConnectivityStatus'; -export * from './IntegrationClient'; -export * from './IntegrationType'; -export * from './Project'; -export * from './IntegrationOptions'; diff --git a/src/index.ts b/src/index.ts index 60b5a5c5..3c7f396d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,8 +11,7 @@ import { StopScan, UploadArchive, VersionCommand, - Configure, - Integration + Configure } from './Commands'; import { CliBuilder, container } from './Config'; @@ -25,7 +24,6 @@ container.resolve(CliBuilder).build({ new RetestScan(), new StopScan(), new UploadArchive(), - new Configure(), - new Integration() + new Configure() ] }).argv;