Skip to content

Commit

Permalink
✨ Added connections for all cybersec integrations
Browse files Browse the repository at this point in the history
  • Loading branch information
naelob committed Aug 16, 2024
1 parent 2e62b82 commit aef9526
Show file tree
Hide file tree
Showing 14 changed files with 1,641 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/api/src/@core/connections/connections.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ProductivityConnectionsModule } from './productivity/productivity.conne
import { MarketingAutomationConnectionsModule } from './marketingautomation/marketingautomation.connection.module';
import { TicketingConnectionModule } from './ticketing/ticketing.connection.module';
import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.module';
import { CybersecurityConnectionsModule } from './cybersecurity/cybersecurity.connection.module';

@Module({
controllers: [ConnectionsController],
Expand All @@ -25,6 +26,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu
FilestorageConnectionModule,
HrisConnectionModule,
EcommerceConnectionModule,
CybersecurityConnectionsModule,
SyncModule,
],
providers: [ValidateUserService, OAuthTokenRefreshService],
Expand All @@ -40,6 +42,7 @@ import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.modu
EcommerceConnectionModule,
HrisConnectionModule,
ProductivityConnectionsModule,
CybersecurityConnectionsModule,
],
})
export class ConnectionsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { EnvironmentService } from '@@core/@core-services/environment/environment.service';
import { BullQueueModule } from '@@core/@core-services/queues/queue.module';
import { WebhookModule } from '@@core/@core-services/webhooks/panora-webhooks/webhook.module';
import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';
import { Module } from '@nestjs/common';
import { CybersecurityConnectionsService } from './services/cybersecurity.connection.service';
import { ServiceRegistry } from './services/registry.service';
import { TenableConnectionService } from './services/tenable/tenable.service';
import { QualysConnectionService } from './services/qualys/qualys.service';
import { SemgrepConnectionService } from './services/semgrep/semgrep.service';
import { SentineloneConnectionService } from './services/sentinelone/sentinelone.service';
import { Rapid7ConnectionService } from './services/rapid7insightvm/rapid7.service';
import { SnykConnectionService } from './services/snyk/snyk.service';
import { CrowdstrikeConnectionService } from './services/crowdstrike/crowdstrike.service';
import { MicrosoftdefenderConnectionService } from './services/microsoftdefender/microsoftdefender.service';

@Module({
imports: [WebhookModule, BullQueueModule],
providers: [
CybersecurityConnectionsService,
WebhookService,
EnvironmentService,
ServiceRegistry,
ConnectionsStrategiesService,
//PROVIDERS SERVICES
SemgrepConnectionService,
TenableConnectionService,
QualysConnectionService,
SentineloneConnectionService,
Rapid7ConnectionService,
SnykConnectionService,
CrowdstrikeConnectionService,
MicrosoftdefenderConnectionService,
],
exports: [CybersecurityConnectionsService],
})
export class CybersecurityConnectionsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { PrismaService } from '@@core/@core-services/prisma/prisma.service';
import { RetryHandler } from '@@core/@core-services/request-retry/retry.handler';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';
import { ConnectionUtils } from '@@core/connections/@utils';
import {
AbstractBaseConnectionService,
OAuthCallbackParams,
PassthroughInput,
RefreshParams,
} from '@@core/connections/@utils/types';
import { PassthroughResponse } from '@@core/passthrough/types';
import { Injectable } from '@nestjs/common';
import {
AuthStrategy,
CONNECTORS_METADATA,
OAuth2AuthData,
providerToType,
} from '@panora/shared';
import { v4 as uuidv4 } from 'uuid';
import { ServiceRegistry } from '../registry.service';
import { EnvironmentService } from '@@core/@core-services/environment/environment.service';
import axios from 'axios';

export interface CrowdstrikeOAuthResponse {
access_token: string;
token_type: string;
scope: string;
}

@Injectable()
export class CrowdstrikeConnectionService extends AbstractBaseConnectionService {
private readonly type: string;

constructor(
protected prisma: PrismaService,
private logger: LoggerService,
protected cryptoService: EncryptionService,
private env: EnvironmentService,
private registry: ServiceRegistry,
private connectionUtils: ConnectionUtils,
private cService: ConnectionsStrategiesService,
private retryService: RetryHandler,
) {
super(prisma, cryptoService);
this.logger.setContext(CrowdstrikeConnectionService.name);
this.registry.registerService('crowdstrike', this);
this.type = providerToType(
'crowdstrike',
'cybersecurity',
AuthStrategy.oauth2,
);
}

async passthrough(
input: PassthroughInput,
connectionId: string,
): Promise<PassthroughResponse> {
try {
const { headers } = input;
const config = await this.constructPassthrough(input, connectionId);

const connection = await this.prisma.connections.findUnique({
where: {
id_connection: connectionId,
},
});

const access_token = JSON.parse(
this.cryptoService.decrypt(connection.access_token),
);
config.headers = {
...config.headers,
...headers,
Authorization: `Bearer ${access_token}`,
};

return await this.retryService.makeRequest(
{
method: config.method,
url: config.url,
data: config.data,
headers: config.headers,
},
'cybersecurity.crowdstrike.passthrough',
config.linkedUserId,
);
} catch (error) {
throw error;
}
}

async handleCallback(opts: OAuthCallbackParams) {
try {
const { linkedUserId, projectId, code } = opts;
const isNotUnique = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'crowdstrike',
vertical: 'cybersecurity',
},
});
//reconstruct the redirect URI that was passed in the frontend it must be the same
const REDIRECT_URI = `${
this.env.getDistributionMode() == 'selfhost'
? this.env.getTunnelIngress()
: this.env.getPanoraBaseUrl()
}/connections/oauth/callback`;

const CREDENTIALS = (await this.cService.getCredentials(
projectId,
this.type,
)) as OAuth2AuthData;

const formData = new URLSearchParams({
redirect_uri: REDIRECT_URI,
client_id: CREDENTIALS.CLIENT_ID,
client_secret: CREDENTIALS.CLIENT_SECRET,
code: code,
grant_type: 'authorization_code',
});
const res = await axios.post(
'https://api.crowdstrike.com/oauth/access_token',
formData.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
},
);
const data: CrowdstrikeOAuthResponse = res.data;
// save tokens for this customer inside our db
let db_res;
const connection_token = uuidv4();
const BASE_API_URL = CONNECTORS_METADATA['cybersecurity']['crowdstrike']
.urls.apiUrl as string;
// get the site id for the token
const site = await axios.get('https://api.crowdstrike.com/v2/sites', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Bearer ${data.access_token}`,
},
});
const site_id = site.data.sites[0].id;
if (isNotUnique) {
// Update existing connection
db_res = await this.prisma.connections.update({
where: {
id_connection: isNotUnique.id_connection,
},
data: {
access_token: this.cryptoService.encrypt(data.access_token),
status: 'valid',
created_at: new Date(),
},
});
} else {
// Create new connection
db_res = await this.prisma.connections.create({
data: {
id_connection: uuidv4(),
connection_token: connection_token,
provider_slug: 'crowdstrike',
vertical: 'cybersecurity',
token_type: 'oauth2',
account_url: `${BASE_API_URL}/sites/${site_id}`,
access_token: this.cryptoService.encrypt(data.access_token),
status: 'valid',
created_at: new Date(),
projects: {
connect: { id_project: projectId },
},
linked_users: {
connect: {
id_linked_user: await this.connectionUtils.getLinkedUserId(
projectId,
linkedUserId,
),
},
},
},
});
}
this.logger.log('Successfully added tokens inside DB ' + db_res);
return db_res;
} catch (error) {
throw error;
}
}

async handleTokenRefresh(opts: RefreshParams) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { PrismaService } from '@@core/@core-services/prisma/prisma.service';
import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service';
import {
CallbackParams,
IConnectionCategory,
PassthroughInput,
RefreshParams,
} from '@@core/connections/@utils/types';
import { Injectable } from '@nestjs/common';
import { connections as Connection } from '@prisma/client';
import { v4 as uuidv4 } from 'uuid';
import { ServiceRegistry } from './registry.service';
import { CategoryConnectionRegistry } from '@@core/@core-services/registries/connections-categories.registry';
import { PassthroughResponse } from '@@core/passthrough/types';

@Injectable()
export class CybersecurityConnectionsService implements IConnectionCategory {
constructor(
private serviceRegistry: ServiceRegistry,
private connectionCategoryRegistry: CategoryConnectionRegistry,
private webhook: WebhookService,
private logger: LoggerService,
private prisma: PrismaService,
) {
this.logger.setContext(CybersecurityConnectionsService.name);
this.connectionCategoryRegistry.registerService('cybersecurity', this);
}
//STEP 1:[FRONTEND STEP]
//create a frontend SDK snippet in which an authorization embedded link is set up so when users click
// on it to grant access => they grant US the access and then when confirmed
/*const authUrl =
'https://app.hubspot.com/oauth/authorize' +
`?client_id=${encodeURIComponent(CLIENT_ID)}` +
`&scope=${encodeURIComponent(SCOPES)}` +
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;*/ //oauth/callback

// oauth server calls this redirect callback
// WE WOULD HAVE CREATED A DEV ACCOUNT IN THE 5 CRMs (Panora dev account)
// we catch the tmp token and swap it against oauth2 server for access/refresh tokens
// to perform actions on his behalf
// this call pass 1. integrationID 2. CustomerId 3. Panora Api Key
async handleCallBack(
providerName: string,
callbackOpts: CallbackParams,
type_strategy: 'oauth2' | 'apikey' | 'basic',
) {
try {
const serviceName = providerName.toLowerCase();

const service = this.serviceRegistry.getService(serviceName);

if (!service) {
throw new ReferenceError(`Unknown provider, found ${providerName}`);
}
const data: Connection = await service.handleCallback(callbackOpts);
const event = await this.prisma.events.create({
data: {
id_connection: data.id_connection,
id_project: data.id_project,
id_event: uuidv4(),
status: 'success',
type: 'connection.created',
method: 'GET',
url: `/${type_strategy}/callback`,
provider: providerName.toLowerCase(),
direction: '0',
timestamp: new Date(),
id_linked_user: callbackOpts.linkedUserId,
},
});
//directly send the webhook
await this.webhook.dispatchWebhook(
data,
'connection.created',
callbackOpts.projectId,
event.id_event,
);
} catch (error) {
throw error;
}
}

async handleTokensRefresh(
connectionId: string,
providerName: string,
refresh_token: string,
id_project: string,
account_url?: string,
) {
try {
const serviceName = providerName.toLowerCase();
const service = this.serviceRegistry.getService(serviceName);
if (!service) {
throw new ReferenceError(`Unknown provider, found ${providerName}`);
}
const refreshOpts: RefreshParams = {
connectionId: connectionId,
refreshToken: refresh_token,
account_url: account_url,
projectId: id_project,
};
await service.handleTokenRefresh(refreshOpts);
} catch (error) {
throw error;
}
}

async passthrough(
input: PassthroughInput,
connectionId: string,
): Promise<PassthroughResponse> {
try {
const connection = await this.prisma.connections.findUnique({
where: {
id_connection: connectionId,
},
});
const serviceName = connection.provider_slug.toLowerCase();
const service = this.serviceRegistry.getService(serviceName);
if (!service) {
throw new ReferenceError(`Unknown provider, found ${serviceName}`);
}
return await service.passthrough(input, connectionId);
} catch (error) {
throw error;
}
}
}
Loading

0 comments on commit aef9526

Please sign in to comment.