Skip to content

Commit

Permalink
Merge branch 'master' into AL-SQS-Local
Browse files Browse the repository at this point in the history
  • Loading branch information
aloftus23 committed Feb 13, 2024
2 parents 2872a23 + feb9db3 commit 9723d53
Show file tree
Hide file tree
Showing 28 changed files with 1,056 additions and 924 deletions.
8 changes: 7 additions & 1 deletion backend/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ custom:
basePath: ''
certificateName: ${file(env.yml):${self:provider.stage}.DOMAIN, ''}
stage: ${self:provider.stage}
createRoute53Record: true
createRoute53Record: false

provider:
name: aws
region: us-east-1
endpointType: REGIONAL
runtime: nodejs16.x
timeout: 30
stage: ${opt:stage, 'dev'}
Expand All @@ -26,6 +27,11 @@ provider:
binaryMediaTypes:
- 'image/*'
- 'font/*'
resourcePolicy:
- Effect: Allow
Principal: '*'
Action: 'execute-api:Invoke'
Resource: 'execute-api:/${self:provider.stage}/*/*'
logs:
restApi: true
deploymentBucket:
Expand Down
8 changes: 8 additions & 0 deletions backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as cors from 'cors';
import * as helmet from 'helmet';
import { handler as healthcheck } from './healthcheck';
import * as auth from './auth';
import * as cpes from './cpes';
import * as cves from './cves';
import * as domains from './domains';
import * as search from './search';
import * as vulnerabilities from './vulnerabilities';
Expand Down Expand Up @@ -287,6 +289,12 @@ authenticatedRoute.delete('/api-keys/:keyId', handlerToExpress(apiKeys.del));

authenticatedRoute.post('/search', handlerToExpress(search.search));
authenticatedRoute.post('/search/export', handlerToExpress(search.export_));
authenticatedRoute.get('/cpes/:id', handlerToExpress(cpes.get));
authenticatedRoute.get('/cves/:cve_uid', handlerToExpress(cves.get));
authenticatedRoute.get(
'/cves/name/:cve_name',
handlerToExpress(cves.getByName)
);
authenticatedRoute.post('/domain/search', handlerToExpress(domains.list));
authenticatedRoute.post('/domain/export', handlerToExpress(domains.export_));
authenticatedRoute.get('/domain/:domainId', handlerToExpress(domains.get));
Expand Down
39 changes: 39 additions & 0 deletions backend/src/api/cpes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ProductInfo, connectToDatabase } from '../models';
import { wrapHandler, NotFound } from './helpers';

// TODO: Join cves to cpe get method
// TODO: Create CpeFilters and CpeSearch classes to handle filtering and pagination of additional fields

/**
* @swagger
* /cpes/{id}:
* get:
* description: Retrieve a CPE by ID
* tags:
* - CPEs
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
*/
export const get = wrapHandler(async (event) => {
const connection = await connectToDatabase();
const repository = connection.getRepository(ProductInfo);

const id = event.pathParameters?.id;
if (!id) {
return NotFound;
}

const productInfo = await repository.findOne(id);
if (!productInfo) {
return NotFound;
}

return {
statusCode: 200,
body: JSON.stringify(productInfo)
};
});
79 changes: 79 additions & 0 deletions backend/src/api/cves.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Cve, connectToDatabase } from '../models';
import { wrapHandler } from './helpers';

// TODO: Add test for joining product_info
// TODO: Create CveFilters and CveSearch classes to handle filtering and pagination of additional fields

/**
* @swagger
* /cves/{cve_uid}:
* get:
* description: Retrieve a CVE by ID.
* tags:
* - CVEs
* parameters:
* - in: path
* name: cve_uid
* required: true
* schema:
* type: string
*/
export const get = wrapHandler(async (event) => {
await connectToDatabase();
const cve_uid = event.pathParameters?.cve_uid;

const cve = await Cve.createQueryBuilder('cve')
.leftJoinAndSelect('cve.product_info', 'product_info')
.where('cve.cve_uid = :cve_uid', { cve_uid: cve_uid })
.getOne();

if (!cve) {
return {
statusCode: 404,
body: JSON.stringify(Error)
};
}

return {
statusCode: 200,
body: JSON.stringify(cve)
};
});

//TODO: Remove getByName endpoint once a one-to-one relationship is established between vulnerability.cve and cve.cve_id
/**
* @swagger
*
* /cves/name/{cve_name}:
* get:
* description: Retrieve a single CVE record by its name.
* tags:
* - CVE
* parameters:
* - name: cve_name
* in: path
* required: true
* schema:
* type: string
*/
export const getByName = wrapHandler(async (event) => {
await connectToDatabase();
const cve_name = event.pathParameters?.cve_name;

const cve = await Cve.createQueryBuilder('cve')
.leftJoinAndSelect('cve.product_info', 'product_info')
.where('cve.cve_name = :cve_name', { cve_name })
.getOne();

if (!cve) {
return {
statusCode: 404,
body: JSON.stringify(Error)
};
}

return {
statusCode: 200,
body: JSON.stringify(cve)
};
});
10 changes: 7 additions & 3 deletions backend/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,13 @@ class UserSearch {
async getResults(event): Promise<[User[], number]> {
const pageSize = this.pageSize || 25;
const sort = this.sort === 'name' ? 'user.fullName' : 'user.' + this.sort;
const qs = User.createQueryBuilder('user').orderBy(sort, this.order);
const results = await qs.getManyAndCount();
return results;
const qs = User.createQueryBuilder('user')
.leftJoinAndSelect('user.roles', 'roles') // Include the roles relation
.leftJoinAndSelect('roles.organization', 'organization') // Include the organization relation
.orderBy(sort, this.order);
const result = await qs.getMany();
const count = await qs.getCount();
return [result, count];
}
}

Expand Down
7 changes: 3 additions & 4 deletions backend/src/api/vulnerabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ class VulnerabilitySearch {
: `vulnerability.${this.sort}`;
let qs = Vulnerability.createQueryBuilder('vulnerability')
.leftJoinAndSelect('vulnerability.domain', 'domain')
.leftJoinAndSelect('domain.organization', 'organization');
.leftJoinAndSelect('domain.organization', 'organization')
.leftJoinAndSelect('vulnerability.service', 'service');

if (groupBy) {
qs = qs
Expand All @@ -185,9 +186,7 @@ class VulnerabilitySearch {
])
.orderBy('cnt', 'DESC');
} else {
qs = qs
.leftJoinAndSelect('vulnerability.service', 'service')
.orderBy(sort, this.order);
qs = qs.orderBy(sort, this.order);
}

if (pageSize !== -1) {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/models/cve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import {
} from 'typeorm';
import { ProductInfo } from './product-info';

//TODO: Refactor column names to camelCase to match the rest of the codebase?
@Entity()
@Unique(['cve_name'])
export class Cve extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
cve_uid: string;
cve_uid: string; //TODO: Refactor to id to match other UUIDs?

@Column({ nullable: true })
cve_name: string;
Expand Down
7 changes: 3 additions & 4 deletions backend/src/models/product-info.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { mapping } from 'src/tasks/censys/mapping';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToMany,
BaseEntity,
Unique
} from 'typeorm';
import { Cve } from './cve';

//TODO: Refactor column names to camelCase to match the rest of the codebase?
//TODO: Refactor table name to product or cpe for brevity?
@Entity()
@Unique(['cpe_product_name', 'version_number', 'vender'])
export class ProductInfo extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string; //TODO: change this to something else??
id: string;

@Column()
cpe_product_name: string;
Expand Down
51 changes: 51 additions & 0 deletions backend/test/cpes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as request from 'supertest';
import app from '../src/api/app';
import { Organization, ProductInfo, connectToDatabase } from '../src/models';
import { createUserToken } from './util';

describe('cpes', () => {
let connection;
let organization: Organization;
let productInfo: ProductInfo;
beforeAll(async () => {
connection = await connectToDatabase();
productInfo = ProductInfo.create({
last_seen: new Date(),
cpe_product_name: 'Test Product',
version_number: '1.0.0',
vender: 'Test Vender'
});
await productInfo.save();
organization = Organization.create({
name: 'test-' + Math.random(),
rootDomains: ['test-' + Math.random()],
ipBlocks: [],
isPassive: false
});
await organization.save();
});

afterAll(async () => {
await ProductInfo.delete(productInfo.id);
await connection.close();
});

describe('CPE API', () => {
it('should return a single CPE by id', async () => {
const response = await request(app)
.get(`/cpes/${productInfo.id}`)
.set(
'Authorization',
createUserToken({
roles: [{ org: organization.id, role: 'user' }]
})
)
.send({})
.expect(200);
expect(response.body.id).toEqual(productInfo.id);
expect(response.body.cpe_product_name).toEqual(
productInfo.cpe_product_name
);
});
});
});
63 changes: 63 additions & 0 deletions backend/test/cves.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as request from 'supertest';
import app from '../src/api/app';
import { Cve, Organization, connectToDatabase } from '../src/models';
import { createUserToken } from './util';

// TODO: Add test for joining product_info
describe('cves', () => {
let connection;
let cve: Cve;
let organization: Organization;
beforeAll(async () => {
connection = await connectToDatabase();
cve = Cve.create({
cve_name: 'CVE-0001-0001'
});
await cve.save();
organization = Organization.create({
name: 'test-' + Math.random(),
rootDomains: ['test-' + Math.random()],
ipBlocks: [],
isPassive: false
});
await organization.save();
});

afterAll(async () => {
await Cve.delete(cve.cve_uid);
await Organization.delete(organization.id);
await connection.close();
});
describe('CVE API', () => {
it('should return a single CVE by cve_name', async () => {
const response = await request(app)
.get(`/cves/name/${cve.cve_name}`)
.set(
'Authorization',
createUserToken({
roles: [{ org: organization.id, role: 'user' }]
})
)
.send({})
.expect(200);
expect(response.body.cve_uid).toEqual(cve.cve_uid);
expect(response.body.cve_name).toEqual(cve.cve_name);
});
});
describe('CVE API', () => {
it('should return a single CVE by cve_uid', async () => {
const response = await request(app)
.get(`/cves/${cve.cve_uid}`)
.set(
'Authorization',
createUserToken({
roles: [{ org: organization.id, role: 'user' }]
})
)
.send({})
.expect(200);
expect(response.body.cve_uid).toEqual(cve.cve_uid);
expect(response.body.cve_name).toEqual(cve.cve_name);
});
});
});
2 changes: 1 addition & 1 deletion frontend/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ custom:
provider:
name: aws
region: us-east-1
endpointType: PRIVATE
endpointType: REGIONAL
runtime: nodejs16.x
timeout: 30
stage: ${opt:stage, 'dev'}
Expand Down
Loading

0 comments on commit 9723d53

Please sign in to comment.