Skip to content

Commit

Permalink
Merge pull request #150 from bcgov/bugfix/updateBucketDiff
Browse files Browse the repository at this point in the history
Allow updateBucket to handle differential patch value checking
  • Loading branch information
kamorel authored Apr 5, 2023
2 parents ab28243 + fcd8233 commit a4e5749
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 24 deletions.
12 changes: 11 additions & 1 deletion app/src/components/errorToProblem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ const Problem = require('api-problem');

const log = require('./log')(module.filename);

/**
* @function errorToProblem
* Attempts to interpret and infer the type of Problem to respond with
* @param {string} service A string representing which service the error occured at
* @param {Error} e The raw error exception object
* @returns {Problem} A problem error type
*/
function errorToProblem(service, e) {
// If already problem type, just return as is
if (e instanceof Problem) {
Expand All @@ -24,7 +31,10 @@ function errorToProblem(service, e) {
});
}
// Something else happened but there's a response
return new Problem(e.response.status, { detail: e.response.data.toString() });
return new Problem(e.response.status, { detail: e.response.data });
} else if (e.statusCode) {
// Handle errors with Status Codes
return new Problem(e.statusCode, { detail: e.message });
} else if (e.$metadata && e.$metadata.httpStatusCode) {
// Handle S3 promise rejections
if (e.$response && e.$response.body) delete e.$response.body;
Expand Down
39 changes: 21 additions & 18 deletions app/src/controllers/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,18 @@ const controller = {
/**
* @function _validateCredentials
* Guard against creating or update a bucket with invalid creds
* @param {object} requestBody The body of the request
* @param {object} credentials The body of the request
* @throws A conflict error problem if the bucket is not reachable
*/
async _validateCredentials(requestBody, res) {
async _validateCredentials(credentials) {
try {
const bucketSettings = {
accessKeyId: requestBody.accessKeyId,
bucket: requestBody.bucket,
endpoint: requestBody.endpoint,
key: requestBody.key,
region: requestBody.region || DEFAULTREGION,
secretAccessKey: requestBody.secretAccessKey,
accessKeyId: credentials.accessKeyId,
bucket: credentials.bucket,
endpoint: credentials.endpoint,
key: credentials.key,
region: credentials.region || DEFAULTREGION,
secretAccessKey: credentials.secretAccessKey,
};
await storageService.headBucket(bucketSettings);
} catch (e) {
Expand All @@ -87,9 +88,8 @@ const controller = {
function: '_validateCredentials',
});
throw new Problem(409, {
details:
'Unable to validate supplied key/password for the supplied object store or bucket',
}).send(res);
details: 'Unable to validate supplied credentials for the bucket',
});
}
},

Expand All @@ -105,10 +105,9 @@ const controller = {
const data = { ...req.body };
let response = undefined;

// Check for credential accessibility/validity first
await controller._validateCredentials(data, res);

try {
// Check for credential accessibility/validity first
await controller._validateCredentials(data);
data.userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, SYSTEM_USER));

response = await bucketService.create(data);
Expand Down Expand Up @@ -216,13 +215,17 @@ const controller = {
* @returns {function} Express middleware function
*/
async updateBucket(req, res, next) {
// Check for credential accessibility/validity first
await controller._validateCredentials(req.body, res);

try {
const bucketId = addDashesToUuid(req.params.bucketId);
const currentBucket = await bucketService.read(bucketId);

// Check for credential accessibility/validity first
// Need to cross reference with existing data when partial patch data is provided
await controller._validateCredentials({ ...currentBucket, ...req.body });

const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, SYSTEM_USER), SYSTEM_USER);
const response = await bucketService.update({
bucketId: req.params.bucketId,
bucketId: bucketId,
bucketName: req.body.bucketName,
accessKeyId: req.body.accessKeyId,
bucket: req.body.bucket,
Expand Down
49 changes: 49 additions & 0 deletions app/tests/unit/components/errorToProblem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,55 @@ describe('errorToProblem', () => {
expect(result.errors).toBeUndefined();
});

it('should return a 409 problem given an error', () => {
const e = {
response: {
data: { detail: 'detail' },
status: 409
}
};
const result = errorToProblem(SERVICE, e);

expect(result).toBeTruthy();
expect(result instanceof Problem).toBeTruthy();
expect(result.title).toMatch('Conflict');
expect(result.status).toBe(409);
expect(result.detail).toEqual(expect.objectContaining(e.response.data));
expect(result.errors).toBeUndefined();
});

it('should return a problem given an error with statusCode', () => {
const e = {
statusCode: 404,
message: 'NotFoundError'
};
const result = errorToProblem(SERVICE, e);

expect(result).toBeTruthy();
expect(result instanceof Problem).toBeTruthy();
expect(result.title).toMatch('Not Found');
expect(result.status).toBe(404);
expect(result.detail).toMatch(e.message);
expect(result.errors).toBeUndefined();
});

it('should return a problem given an error with s3 metadata', () => {
const e = {
$metadata: {
httpStatusCode: 404,
},
message: 'NotFoundError'
};
const result = errorToProblem(SERVICE, e);

expect(result).toBeTruthy();
expect(result instanceof Problem).toBeTruthy();
expect(result.title).toMatch('Not Found');
expect(result.status).toBe(404);
expect(result.detail).toEqual(expect.objectContaining({ message: e.message }));
expect(result.errors).toBeUndefined();
});

it('should return a 422 problem with a supplied string response', () => {
const e = {
response: {
Expand Down
10 changes: 6 additions & 4 deletions app/tests/unit/controllers/bucket.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('createBucket', () => {
);
});

it('excepts if invalid bucket head', async () => {
it('responds with error when invalid bucket head', async () => {
// request object
const req = {
body: { bucket: REQUEST_BUCKET, region: 'test' },
Expand All @@ -126,13 +126,15 @@ describe('createBucket', () => {
throw new Error();
});

await expect(controller.createBucket(req, res, next)).rejects.toThrow();
await controller.createBucket(req, res, next);

expect(headBucketSpy).toHaveBeenCalledTimes(1);
expect(headBucketSpy).toHaveBeenCalledWith(req.body);
expect(getCurrentIdentitySpy).toHaveBeenCalledTimes(0);
expect(getCurrentUserIdSpy).toHaveBeenCalledTimes(0);
expect(createSpy).toHaveBeenCalledTimes(0);
expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(expect.any(Problem));
});

it('nexts an error if the bucket service fails to create', async () => {
Expand Down Expand Up @@ -167,7 +169,7 @@ describe('createBucket', () => {
expect(getCurrentUserIdSpy).toHaveBeenCalledWith(USR_IDENTITY);
expect(createSpy).toHaveBeenCalledTimes(1);
expect(createSpy).toHaveBeenCalledWith({ ...req.body, userId: USR_ID });

expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(
new Problem(502, 'Unknown BucketService Error')
Expand Down Expand Up @@ -213,7 +215,7 @@ describe('createBucket', () => {
expect(createSpy).toHaveBeenCalledWith({ ...req.body, userId: USR_ID });
expect(checkGrantPermissionsSpy).toHaveBeenCalledTimes(1);
expect(checkGrantPermissionsSpy).toHaveBeenCalledWith({ ...req.body, userId: USR_ID });

expect(res.status).toHaveBeenCalledWith(201);
});
});
Expand Down
2 changes: 1 addition & 1 deletion charts/coms/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: common-object-management-service
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.11
version: 0.0.12
kubeVersion: ">= 1.13.0"
description: A microservice for managing access control to S3 Objects
# A chart can be either an 'application' or a 'library' chart.
Expand Down

0 comments on commit a4e5749

Please sign in to comment.