Skip to content
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
45 changes: 2 additions & 43 deletions src/nodejs/controllers/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1024,50 +1024,9 @@ module.exports.fileUpload = function fileUpload(req, res, next) {
const { payload } = params;
const lambdaEvent = {
resource: 'upload',
operation: 'getPostUrl',
context: { user_id: req.user_id },
...payload.value
};
handlers.fileUpload(lambdaEvent).then((body) => {
setTimeout(() => res.send(body), latency);
});
};

module.exports.groupFileUpload = function groupFileUpload(req, res, next) {
const { params } = req.swagger;
const { payload } = params;
const lambdaEvent = {
resource: 'upload',
operation: 'getGroupUploadUrl',
context: { user_id: req.user_id },
...payload.value
};
handlers.fileUpload(lambdaEvent).then((body) => {
setTimeout(() => res.send(body), latency);
});
};

module.exports.attachmentFileUpload = function attachmentFileUpload(req, res, next) {
const { params } = req.swagger;
const { payload } = params;
const lambdaEvent = {
resource: 'upload',
operation: 'getAttachmentUploadUrl',
context: { user_id: req.user_id },
...payload.value
};
handlers.fileUpload(lambdaEvent).then((body) => {
setTimeout(() => res.send(body), latency);
});
};

module.exports.uploadStepUrl = function uploadStepUrl(req, res, next) {
const { params } = req.swagger;
const { payload } = params;
const lambdaEvent = {
resource: 'upload',
operation: 'getUploadStepUrl',
operation: 'getUrl',
context: { user_id: req.user_id },
upload_type: params.type.value,
...payload.value
};
handlers.fileUpload(lambdaEvent).then((body) => {
Expand Down
141 changes: 88 additions & 53 deletions src/nodejs/lambda-handlers/file-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,12 @@ async function generateUploadUrl(params) {
}
}

async function getPostUrlMethod(event, user) {
async function getFormUrlKeyMethod(event, user) {
const {
file_name: fileName,
file_type: fileType,
checksum_value: checksumValue,
file_size_bytes: fileSize
file_category: fileCategory,
submission_id: submissionId
} = event;
const { file_category: fileCategory } = event;
const { submission_id: submissionId } = event;
const userInfo = await db.user.findById({ id: user });
const groupIds = userInfo.user_groups.map((group) => group.id);

Expand All @@ -129,87 +126,128 @@ async function getPostUrlMethod(event, user) {
|| userInfo.user_privileges.includes('ADMIN')
|| userDaacIds.includes(daacId)
) {
return generateUploadUrl({
key: `${submissionId}/${fileCategory}/${user}/${fileName}`,
checksumValue,
fileType,
fileCategory,
fileSize
});
return ({ key: `${submissionId}/${fileCategory}/${user}/${fileName}` });
}
}
return generateUploadUrl({
key: `${fileCategory}/${user}/${fileName}`,
checksumValue,
fileType,
fileCategory,
fileSize
});

// return `${fileCategory}/${user}/${fileName}`
return ({ error: 'Not Implemented' });
}

async function getGroupUploadUrlMethod(event, user) {
async function getGroupUploadKeyMethod(event, user) {
const {
file_name: fileName,
file_type: fileType,
checksum_value: checksumValue,
prefix,
file_size_bytes: fileSize
prefix
} = event;
const { group_id: groupId } = event;
const userInfo = await db.user.findById({ id: user });
const groupIds = userInfo.user_groups.map((group) => group.id);
const rootGroupId = '4daa6b22-f015-4ce2-8dac-8b3510004fca';

const groupShortName = (await db.group.findById({ id: groupId })).short_name;
if (!groupShortName) {
return ({ error: 'Invalid Group' });
}

if (!(userInfo.user_privileges.includes('ADMIN') || groupIds.includes(rootGroupId))
&& !(groupIds.includes(groupId) && userInfo.user_privileges.includes('GROUP_UPLOAD'))) {
return ({ error: 'Not Authorized' });
}
const groupShortName = (await db.group.findById({ id: groupId })).short_name;

const key = prefix ? `group/${groupShortName}/${prefix.replace(/^\/?/, '').replace(/\/?$/, '')}/${fileName}` : `group/${groupShortName}/${fileName}`;
return generateUploadUrl({
key,
checksumValue,
fileType,
fileSize
});
return ({ key });
}

async function getAttachmentUploadUrlMethod(event, user) {
async function getAttachmentUploadKeyMethod(event, user) {
const {
file_name: fileName,
file_type: fileType,
checksum_value: checksumValue,
conversation_id: conversationId,
file_size_bytes: fileSize
conversation_id: conversationId
} = event;
const userInfo = await db.user.findById({ id: user });

if (!userInfo.user_privileges.includes('ADMIN') && !userInfo.user_privileges.includes('NOTE_REPLY')) {
return ({ error: 'Not Authorized' });
}
// Verify conversation is real & the user has permissions!
let response = {};
if (userInfo.user_privileges.includes('ADMIN')
|| userInfo.user_groups.some((group) => group.short_name === 'root_group')) {
response = await db.note.readConversation({
admin: true,
conversation_id: conversationId
});
} else if (userInfo.user_privileges.includes('REQUEST_DAACREAD')) {
response = await db.note.readConversation({
user_id: user,
daac: true,
conversation_id: conversationId
});
} else {
response = await db.note.readConversation({
user_id: user,
conversation_id: conversationId
});
}

if (response.error) {
return ({ error: 'Conversation not found' });
}

const key = `drafts/${conversationId}/${user}/${fileName}`;
return generateUploadUrl({
key,
checksumValue,
fileType,
fileSize
});
return ({ key });
}

async function getUploadStepUrlMethod(event, user) {
async function getUploadStepKeyMethod(event, user) {
const {
file_name: fileName,
file_type: fileType,
checksum_value: checksumValue,
file_category: fileCategory,
destination: uploadDestination,
submission_id: submissionId,
file_size_bytes: fileSize
submission_id: submissionId
} = event;

if (!fileName || !fileCategory || !uploadDestination || !submissionId) {
return ({ error: 'Invlaid parameters' });
}

// Verify submission is real!
const submissionResp = await db.submission.findById({ id: submissionId, user_id: user });
if (submissionResp.error) {
return ({ error: 'Submission not found' });
}

const key = `${uploadDestination.replace(/^\/?/, '').replace(/\/?$/, '')}/${submissionId}/${fileCategory}/${user}/${fileName}`;
return ({ key });
}

const keyMethods = {
form: getFormUrlKeyMethod,
group: getGroupUploadKeyMethod,
attachment: getAttachmentUploadKeyMethod,
step: getUploadStepKeyMethod
};

async function getUploadUrlMethod(event, user) {
const {
upload_type: uploadType,
file_type: fileType,
checksum_value: checksumValue,
file_size_bytes: fileSize
} = event;

if (!(uploadType && Object.keys(keyMethods).includes(uploadType))) {
return ({ error: 'Invalid upload type' });
}

// Get the key for the type of url needed. The key method will perform the needed validations
const uploadKeyGenerator = keyMethods[uploadType];
const keyResult = await uploadKeyGenerator(event, user);

if (keyResult && keyResult.error) {
return ({ error: keyResult.error });
}

return generateUploadUrl({
key,
key: keyResult.key,
checksumValue,
fileType,
fileSize
Expand Down Expand Up @@ -544,14 +582,11 @@ async function getPartUrlMethod(event) {
}

const operations = {
getPostUrl: getPostUrlMethod,
getUrl: getUploadUrlMethod,
listFiles: listFilesMethod,
createTempUploadFile: createTempUploadFileMethod,
listStepFiles: listStepFilesMethod,
getDownloadUrl: getDownloadUrlMethod,
getGroupUploadUrl: getGroupUploadUrlMethod,
getAttachmentUploadUrl: getAttachmentUploadUrlMethod,
getUploadStepUrl: getUploadStepUrlMethod,
getUploadStep: getUploadStepMethod,
completeUpload: completeUploadMethod,
getPartUrl: getPartUrlMethod
Expand Down
31 changes: 24 additions & 7 deletions src/nodejs/lambda-handlers/test/handlers/file-upload.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ db.upload = jest.fn();
db.upload.findUploadStepById = jest.fn();
db.submission.getTempUploadFiles = jest.fn();
db.submission.deleteTempUploadFilesByIds = jest.fn().mockResolvedValue({});
db.note = jest.fn();
db.note.readConversation = jest.fn();

describe('file-upload', () => {
beforeEach(() => {
Expand Down Expand Up @@ -91,7 +93,8 @@ describe('file-upload', () => {
submission_id: 'some_id',
file_category: 'sample',
context: { user_id: 'user_id' },
operation: 'getPostUrl'
operation: 'getUrl',
upload_type: 'form'
};
db.submission.findById.mockReturnValueOnce({
daac_id: 'daac_id',
Expand All @@ -101,25 +104,27 @@ describe('file-upload', () => {
const response = await fileUpload.handler(payload);
expect(response).toEqual(expectUploadResponse);
});
it('should generate an upload url with no submission id', async () => {
it('should generate an error when trying to create a form upload url with no submission id', async () => {
const payload = {
file_name: 'test.txt',
file_type: 'text/plain',
checksum_value: '1234567890',
file_category: 'documentation',
context: { user_id: 'user_id' },
operation: 'getPostUrl'
operation: 'getUrl',
upload_type: 'form'
};
const response = await fileUpload.handler(payload);
expect(response).toEqual(expectUploadResponse);
expect(response).toEqual({ error: 'Not Implemented' });
});
it('should generate a group upload url with no prefix', async () => {
const payload = {
file_name: 'test.txt',
file_type: 'text/plain',
checksum_value: '1234567890',
context: { user_id: 'user_id' },
operation: 'getGroupUploadUrl',
operation: 'getUrl',
upload_type: 'group',
group_id: 'daac_id'
};
db.group.findById.mockReturnValueOnce({
Expand All @@ -136,8 +141,13 @@ describe('file-upload', () => {
conversation_id: '342ba8f5-ea87-4ef4-abf0-8eb0f924115b',
file_size_bytes: 1234,
context: { user_id: 'user_id' },
operation: 'getAttachmentUploadUrl'
upload_type: 'attachment',
operation: 'getUrl'
};
db.note.readConversation.mockReturnValueOnce({
id: 'conversation_id'
});

const response = await fileUpload.handler(payload);
expect(response).toEqual(expectUploadResponse);
});
Expand Down Expand Up @@ -259,10 +269,17 @@ describe('file-upload', () => {
file_name: 'test.txt',
file_type: 'text/plain',
checksum_value: '1234567890',
submission_id: 'some_id',
file_category: 'documentation',
context: { user_id: 'user_id' },
operation: 'getPostUrl'
operation: 'getUrl',
upload_type: 'form'
};
db.submission.findById.mockReturnValueOnce({
daac_id: 'daac_id',
contributor_ids: ['contributor_id']
});
db.daac.getIds.mockReturnValueOnce([{ id: 'daac_id' }]);
jest.spyOn(global, 'fetch').mockImplementation(() => (new Response('<html>Non JSON response</html>')));
const response = await fileUpload.handler(payload);
expect(response).toEqual({ error: 'Error parsing CUE API response.' });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports.model = (path) => ({
description: 'Request body sent to upload',
description: 'Request body sent to generate a group upload key',
type: 'object',
properties: {

Expand All @@ -25,7 +25,9 @@ module.exports.model = (path) => ({
}
},
required: [
'file_type'
'file_name',
'file_type',
'group_id'
]
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports.model = (path) => ({
description: 'Request body sent to upload',
description: 'Request body sent to generate a step upload key',
type: 'object',
allOf: [{ $ref: `#${path}Upload` }],
properties: {
Expand Down
3 changes: 2 additions & 1 deletion src/nodejs/lambda-layers/schema-util/src/models/upload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports.model = (path) => ({
description: 'Request body sent to upload',
description: 'Request body sent to generate a form upload key',
type: 'object',
properties: {

Expand All @@ -26,6 +26,7 @@ module.exports.model = (path) => ({
}
},
required: [
'file_name',
'file_type',
'file_category'
]
Expand Down
Loading