From 60445727e9c33eb60abaa88ac3ebda8f2d06e880 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 31 Dec 2022 17:48:56 -0500 Subject: [PATCH] merge with own change --- backend/src/routes/v2/secret.ts | 94 +++++++++++++++++++++++++++---- backend/src/types/secret/index.ts | 4 ++ package-lock.json | 30 ++++++++++ package.json | 4 ++ 4 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 backend/src/types/secret/index.ts diff --git a/backend/src/routes/v2/secret.ts b/backend/src/routes/v2/secret.ts index fb2a23f42..42badbff8 100644 --- a/backend/src/routes/v2/secret.ts +++ b/backend/src/routes/v2/secret.ts @@ -1,10 +1,15 @@ import express, { Request, Response } from 'express'; -import { requireAuth, validateRequest } from '../../middleware'; +import { requireAuth, requireWorkspaceAuth, validateRequest } from '../../middleware'; import { ISecret, Secret } from '../../models'; import { decryptSymmetric } from '../../utils/crypto'; import { getLogger } from '../../utils/logger'; import { body, param, query, check } from 'express-validator'; -import { BadRequestError } from '../../utils/errors'; +import { BadRequestError, UnauthorizedRequestError } from '../../utils/errors'; +import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; +import { ModifySecretPayload } from '../../types/secret'; +import { AnyBulkWriteOperation } from 'mongodb'; +import to from 'await-to-js'; + const router = express.Router(); /** @@ -13,6 +18,10 @@ const router = express.Router(); router.post( '/', requireAuth, body('secret').exists().isObject(), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), async (req: Request, res: Response) => { try { const { secret }: { secret: ISecret[] } = req.body; @@ -29,6 +38,10 @@ router.post( */ router.post( '/bulk-create', requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), body('secrets').exists().isArray().custom((value) => value.every((item: ISecret) => typeof item === 'object')), async (req: Request, res: Response) => { try { @@ -46,6 +59,10 @@ router.post( */ router.get( '/:secretId', requireAuth, param('secretId').exists().trim(), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), validateRequest, async (req: Request, res: Response) => { try { const secretFromDB = await Secret.findById(req.params.secretId) @@ -61,6 +78,10 @@ router.get( */ router.get( '/:bulk', requireAuth, param('secretId').exists().trim(), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), validateRequest, async (req: Request, res: Response) => { try { const secretFromDB = await Secret.findById(req.params.secretId) @@ -77,6 +98,10 @@ router.get( router.delete( '/:secretId', requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), param('secretId').exists().trim(), validateRequest, async (req: Request, res: Response) => { try { @@ -96,6 +121,10 @@ router.delete( router.delete( '/batch', requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED] + }), body('secretIds').exists().isArray(), validateRequest, async (req: Request, res: Response) => { try { @@ -111,25 +140,66 @@ router.delete( ); /** - * Apply modifications to many existing secrets + * Apply modifications to many existing secrets in a given workspace and environment + * Note: although we do not check access for environments, we will in the future */ router.patch( - '/bulk-update', + '/bulk-modify/:workspaceId/:environmentName', requireAuth, body('secrets').exists().isArray().custom((value) => value.every((item: ISecret) => typeof item === 'object')), + param('workspaceId').exists().trim(), + param('environmentName').exists().trim(), + // requireWorkspaceAuth({ + // acceptedRoles: [ADMIN, MEMBER], + // acceptedStatuses: [COMPLETED, GRANTED] + // }), validateRequest, async (req: Request, res: Response) => { try { - const { secrets }: { secrets: ISecret[] } = req.body; + const { workspaceId, environmentName } = req.params + const secretsModificationsRequested: ModifySecretPayload[] = req.body.secrets; - const operations = secrets.map((secretToUpdate: ISecret) => ({ - updateOne: { filter: { _id: secretToUpdate._id }, update: secretToUpdate }, - })); + const secretsUserCanModify: ISecret[] = await Secret.find({ workspace: workspaceId, environment: environmentName }) - const bulkModificationInfo = await Secret.bulkWrite(operations); + const secretsUserCanModifyMapBySecretId: Map = new Map(); + secretsUserCanModify.forEach(secret => secretsUserCanModifyMapBySecretId.set(secret._id.toString(), secret)) - return res.status(200).json(bulkModificationInfo) - } catch (error) { - throw BadRequestError({ message: `Unable to process the bulk update. Double check the ids of the secrets` }) + // Check if the entity has access to the secret ids it wants to modify + const updateOperationsToPerform: AnyBulkWriteOperation[] = [] + secretsModificationsRequested.forEach(userModifiedSecret => { + const canModifyRequestedSecret = secretsUserCanModifyMapBySecretId.has(userModifiedSecret._id.toString()) + if (canModifyRequestedSecret) { + const oldSecretInDB = secretsUserCanModifyMapBySecretId.get(userModifiedSecret._id.toString()) + + if (oldSecretInDB !== undefined) { + oldSecretInDB.secretKeyCiphertext = userModifiedSecret.secretKeyCiphertext + oldSecretInDB.secretKeyIV = userModifiedSecret.secretKeyIV + oldSecretInDB.secretKeyTag = userModifiedSecret.secretKeyTag + oldSecretInDB.secretKeyHash = userModifiedSecret.secretKeyHash + oldSecretInDB.secretValueCiphertext = userModifiedSecret.secretValueCiphertext + oldSecretInDB.secretValueIV = userModifiedSecret.secretValueIV + oldSecretInDB.secretValueTag = userModifiedSecret.secretValueTag + oldSecretInDB.secretValueHash = userModifiedSecret.secretValueHash + oldSecretInDB.secretCommentCiphertext = userModifiedSecret.secretCommentCiphertext + oldSecretInDB.secretCommentIV = userModifiedSecret.secretCommentIV + oldSecretInDB.secretCommentTag = userModifiedSecret.secretCommentTag + oldSecretInDB.secretCommentHash = userModifiedSecret.secretCommentHash + + const updateOperation = { updateOne: { filter: { _id: oldSecretInDB._id, workspace: oldSecretInDB.workspace }, update: { $inc: { version: 1 }, $set: oldSecretInDB } } } + updateOperationsToPerform.push(updateOperation) + } + } else { + throw UnauthorizedRequestError({ message: "You do not have permission to modify one or more of the requested secrets" }) + } + }) + + const bulkModificationInfo = await Secret.bulkWrite(updateOperationsToPerform); + + return res.status(200).json({ + bulkModificationInfo + }) + + } catch (e) { + throw BadRequestError() } } ); diff --git a/backend/src/types/secret/index.ts b/backend/src/types/secret/index.ts new file mode 100644 index 000000000..11696484e --- /dev/null +++ b/backend/src/types/secret/index.ts @@ -0,0 +1,4 @@ +import { Omit } from 'utility-types'; +import { ISecret } from '../../models'; + +export type ModifySecretPayload = Omit; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cf02a1760..0ba1a0659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,10 @@ "": { "name": "infisical", "license": "ISC", + "dependencies": { + "await-to-js": "^3.0.0", + "utility-types": "^3.10.0" + }, "devDependencies": { "eslint": "^8.29.0", "husky": "^8.0.2" @@ -169,6 +173,14 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1092,6 +1104,14 @@ "punycode": "^2.1.0" } }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1248,6 +1268,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1910,6 +1935,11 @@ "punycode": "^2.1.0" } }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 4d042a13d..5b50c5859 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,9 @@ "devDependencies": { "eslint": "^8.29.0", "husky": "^8.0.2" + }, + "dependencies": { + "await-to-js": "^3.0.0", + "utility-types": "^3.10.0" } }