Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor toggle visibility code/endpoints #527

Merged
merged 9 commits into from
Sep 21, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import * as makePrivate from './makePrivate';
import * as db from '../../../../../database/db';
import * as project_attachments_queries from '../../../../../queries/project/project-attachments-queries';
import SQL from 'sql-template-strings';

chai.use(sinonChai);

describe('makeProjectAttachmentPrivate', () => {
afterEach(() => {
sinon.restore();
});

const dbConnectionObj = {
systemUserId: () => {
return null;
},
open: async () => {
// do nothing
},
release: () => {
// do nothing
},
commit: async () => {
// do nothing
},
rollback: async () => {
// do nothing
},
query: async () => {
// do nothing
}
};

const sampleReq = {
keycloak_token: {},
params: {
projectId: 1,
attachmentId: 2
}
} as any;

it('should throw an error when projectId is missing', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = makePrivate.makeProjectAttachmentPrivate();

await result(
{ ...sampleReq, params: { ...sampleReq.params, projectId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Missing required path param `projectId`');
}
});

it('should throw an error when attachmentId is missing', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = makePrivate.makeProjectAttachmentPrivate();

await result(
{ ...sampleReq, params: { ...sampleReq.params, attachmentId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Missing required path param `attachmentId`');
}
});

it('should throw an error when fails to build getProjectAttachmentSecurityRuleSQL statement', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);
sinon.stub(project_attachments_queries, 'getProjectAttachmentSecurityRuleSQL').returns(null);

try {
const result = makePrivate.makeProjectAttachmentPrivate();

await result(sampleReq, (null as unknown) as any, (null as unknown) as any);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Failed to build SQL get project attachment security rule statement');
}
});

it('should throw an error when fails to build addProjectAttachmentSecurityRuleSQL statement when no project attachment security rule id exists', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({
rows: [
{
id: null
}
]
});

sinon.stub(db, 'getDBConnection').returns({ ...dbConnectionObj, query: mockQuery });
sinon.stub(project_attachments_queries, 'getProjectAttachmentSecurityRuleSQL').returns(SQL`something`);
sinon.stub(project_attachments_queries, 'addProjectAttachmentSecurityRuleSQL').returns(null);

try {
const result = makePrivate.makeProjectAttachmentPrivate();

await result(sampleReq, (null as unknown) as any, (null as unknown) as any);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Failed to build SQL insert project attachment security rule statement');
}
});

it('should throw an error when fails to add project attachment security rule when no project attachment security rule id exists', async () => {
const mockQuery = sinon.stub();

mockQuery
.onFirstCall()
.resolves({
rows: [
{
id: null
}
]
})
.onSecondCall()
.resolves({
rows: [
{
id: null
}
]
});

sinon.stub(db, 'getDBConnection').returns({ ...dbConnectionObj, query: mockQuery });
sinon.stub(project_attachments_queries, 'getProjectAttachmentSecurityRuleSQL').returns(SQL`something`);
sinon.stub(project_attachments_queries, 'addProjectAttachmentSecurityRuleSQL').returns(SQL`something`);

try {
const result = makePrivate.makeProjectAttachmentPrivate();

await result(sampleReq, (null as unknown) as any, (null as unknown) as any);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Failed to insert project attachment security rule');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
'use strict';

import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import {
getProjectAttachmentSecurityRuleSQL,
applyProjectAttachmentSecurityRuleSQL,
addProjectAttachmentSecurityRuleSQL
} from '../../../../../queries/project/project-attachments-queries';
import { SYSTEM_ROLE } from '../../../../../constants/roles';
import { getDBConnection, IDBConnection } from '../../../../../database/db';
import { HTTP400 } from '../../../../../errors/CustomError';
import { getLogger } from '../../../../../utils/logger';

const defaultLog = getLogger('/api/project/{projectId}/attachments/{attachmentId}/makePrivate');

export const PUT: Operation = [makeProjectAttachmentPrivate()];

PUT.apiDoc = {
description: 'Make visibility of a project attachment private.',
tags: ['attachment', 'visibility'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
}
],
parameters: [
{
in: 'path',
name: 'projectId',
schema: {
type: 'number'
},
required: true
},
{
in: 'path',
name: 'attachmentId',
schema: {
type: 'number'
},
required: true
}
],
responses: {
200: {
description: 'Project attachment make private visibility response.',
content: {
'application/json': {
schema: {
title: 'Row count of record for which visibility has been made private',
type: 'number'
}
}
}
},
401: {
$ref: '#/components/responses/401'
},
default: {
$ref: '#/components/responses/default'
}
}
};

export function makeProjectAttachmentPrivate(): RequestHandler {
return async (req, res) => {
defaultLog.debug({
label: 'Make visibility of a project attachment private',
message: 'params',
req_params: req.params
});

if (!req.params.projectId) {
throw new HTTP400('Missing required path param `projectId`');
}

if (!req.params.attachmentId) {
throw new HTTP400('Missing required path param `attachmentId`');
}

const connection = getDBConnection(req['keycloak_token']);

try {
await connection.open();

// Step 1: Check if security rule already exists
let securityRuleId = await getExistingSecurityToken(Number(req.params.attachmentId), connection);

// Step 2: Create security rule if it does not exist
if (!securityRuleId) {
securityRuleId = await createNewSecurityRule(
Number(req.params.projectId),
Number(req.params.attachmentId),
connection
);
}

// Step 3: Apply the security rule that was fetched or created
await applyProjectAttachmentSecurityRule(securityRuleId, connection);

await connection.commit();

return res.status(200).json(1);
} catch (error) {
defaultLog.debug({ label: 'makeProjectAttachmentPrivate', message: 'error', error });
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
}

export const getExistingSecurityToken = async (
attachmentId: number,
connection: IDBConnection
): Promise<number | null> => {
const getSecurityRuleSQLStatement = getProjectAttachmentSecurityRuleSQL(attachmentId);

if (!getSecurityRuleSQLStatement) {
throw new HTTP400('Failed to build SQL get project attachment security rule statement');
}

const getSecurityRuleSQLResponse = await connection.query(
getSecurityRuleSQLStatement.text,
getSecurityRuleSQLStatement.values
);

return (
(getSecurityRuleSQLResponse &&
getSecurityRuleSQLResponse.rows &&
getSecurityRuleSQLResponse.rows[0] &&
getSecurityRuleSQLResponse.rows[0].id) ||
null
);
};

export const createNewSecurityRule = async (
projectId: number,
attachmentId: number,
connection: IDBConnection
): Promise<number> => {
const createSecurityRuleSQLStatement = addProjectAttachmentSecurityRuleSQL(projectId, attachmentId);

if (!createSecurityRuleSQLStatement) {
throw new HTTP400('Failed to build SQL insert project attachment security rule statement');
}

const createSecurityRuleSQLResponse = await connection.query(
createSecurityRuleSQLStatement.text,
createSecurityRuleSQLStatement.values
);

const securityRuleId =
(createSecurityRuleSQLResponse &&
createSecurityRuleSQLResponse.rows &&
createSecurityRuleSQLResponse.rows[0] &&
createSecurityRuleSQLResponse.rows[0].id) ||
null;

if (!securityRuleId) {
throw new HTTP400('Failed to insert project attachment security rule');
}

return securityRuleId;
};

export const applyProjectAttachmentSecurityRule = async (
securityRuleId: number,
connection: IDBConnection
): Promise<void> => {
const applySecurityRuleSQLStatement = applyProjectAttachmentSecurityRuleSQL(securityRuleId);

if (!applySecurityRuleSQLStatement) {
throw new HTTP400('Failed to build SQL apply project attachment security rule statement');
}

const applySecurityRuleSQLResponse = await connection.query(
applySecurityRuleSQLStatement.text,
applySecurityRuleSQLStatement.values
);

if (!applySecurityRuleSQLResponse) {
throw new HTTP400('Failed to apply project attachment security rule');
}
};
Loading