From 673cdcdf8cbc95ffb011ced101adf4e514d90e93 Mon Sep 17 00:00:00 2001 From: SAMI BETTAYEB Date: Tue, 22 Jun 2021 21:45:11 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Add=20dev=20dependenci?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SAMI BETTAYEB --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index da6c315b..effabaa6 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ }, "devDependencies": { "eslint": "^7.7.0", + "chai": "^4.3.4", + "chai-subset": "^1.6.0", "eslint-config-loopback": "^13.1.0", "juggler-v3": "file:./deps/juggler-v3", "juggler-v4": "file:./deps/juggler-v4", From dcc2d96265c89035ba05ef12b7594c626f2efd72 Mon Sep 17 00:00:00 2001 From: SAMI BETTAYEB Date: Tue, 22 Jun 2021 21:47:42 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=97=83=20Enable=20pgcrypto=20extensio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SAMI BETTAYEB --- lib/migration.js | 3 ++- lib/postgresql.js | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 57ad4310..d20f6888 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -351,7 +351,8 @@ function mixinMigration(PostgreSQL) { }); // default extension if (!createExtensions) { - createExtensions = 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'; + createExtensions = `CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + CREATE EXTENSION IF NOT EXISTS "pgcrypto";`; } // Please note IF NOT EXISTS is introduced in postgresql v9.3 diff --git a/lib/postgresql.js b/lib/postgresql.js index 2ac994f2..84086977 100644 --- a/lib/postgresql.js +++ b/lib/postgresql.js @@ -112,6 +112,7 @@ PostgreSQL.prototype.connect = function(callback) { self.client = client; process.nextTick(releaseCb); callback && callback(err, client); + if (!err) self.execute('CREATE EXTENSION IF NOT EXISTS pgcrypto', function(createExtensionError) {}); }); }; @@ -588,6 +589,17 @@ PostgreSQL.prototype.buildWhere = function(model, where) { return whereClause; }; +PostgreSQL.prototype.getEncryptionFields = function(modelDefinition) { + if (modelDefinition + && modelDefinition.settings + && modelDefinition.settings.mixins + && modelDefinition.settings.mixins.Encryption + && modelDefinition.settings.mixins.Encryption.fields) { + return modelDefinition.settings.mixins.Encryption.fields; + } + return []; +}; + /** * @private * @param model @@ -606,6 +618,7 @@ PostgreSQL.prototype._buildWhere = function(model, where) { const self = this; const props = self.getModelDefinition(model).properties; + const encryptedFields = this.getEncryptionFields(this.getModelDefinition(model)); const whereStmts = []; for (const key in where) { const stmt = new ParameterizedSQL('', []); @@ -646,7 +659,18 @@ PostgreSQL.prototype._buildWhere = function(model, where) { } // eslint-disable one-var let expression = where[key]; - const columnName = self.columnEscaped(model, key); + let columnName = self.columnEscaped(model, key); + if (encryptedFields.includes(key)) { + columnName = `convert_from( + decrypt_iv( + DECODE(${key},'hex')::bytea, + decode('${process.env.ENCRYPTION_HEX_KEY}','hex')::bytea, + decode('${process.env.ENCRYPTION_HEX_IV}','hex')::bytea, + 'aes' + ), + 'utf8' + )`; + } // eslint-enable one-var if (expression === null || expression === undefined) { stmt.merge(columnName + ' IS NULL'); From 490cf87bad987e79bb76b93870ee0b9a13819f89 Mon Sep 17 00:00:00 2001 From: SAMI BETTAYEB Date: Tue, 22 Jun 2021 21:50:47 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=94=A7=20Add=20required=20crypto=20en?= =?UTF-8?q?v=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SAMI BETTAYEB --- test/init.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/init.js b/test/init.js index d2fa8feb..514c5fa0 100644 --- a/test/init.js +++ b/test/init.js @@ -21,6 +21,9 @@ process.env.PGUSER = process.env.POSTGRESQL_USER || process.env.PGPASSWORD = process.env.POSTGRESQL_PASSWORD || process.env.PGPASSWORD || ''; +process.env.ENCRYPTION_HEX_KEY = process.env.ENCRYPTION_HEX_KEY || 'abcdef0123456789abcdef0123456789'; +process.env.ENCRYPTION_HEX_IV = process.env.ENCRYPTION_HEX_IV || '0123456789abcdef0123456789abcdef'; + config = { host: process.env.PGHOST, port: process.env.PGPORT, From 6abaed843aa35455db1d0e1d1f5e4e401d244612 Mon Sep 17 00:00:00 2001 From: SAMI BETTAYEB Date: Tue, 22 Jun 2021 21:51:47 +0100 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=85=20=F0=9F=97=83=20Add=20encrypted?= =?UTF-8?q?=5Fdata=20table=20for=20testing=20purposes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SAMI BETTAYEB --- test/schema.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/schema.sql b/test/schema.sql index 7020812c..cd59447b 100644 --- a/test/schema.sql +++ b/test/schema.sql @@ -377,6 +377,16 @@ CREATE TABLE reservation ( ); +-- +-- Name: encrypted_data; Type: TABLE; Schema: strongloop; Owner: strongloop +-- + +CREATE TABLE encrypted_data ( + id character varying(64), + data text +); + + -- -- Name: session; Type: TABLE; Schema: strongloop; Owner: strongloop -- @@ -1207,6 +1217,8 @@ INSERT INTO product VALUES ('87', 'NV Goggles', NULL, NULL, NULL, NULL, NULL); INSERT INTO product VALUES ('2', 'G17', 53, 75, 15, 'Flashlight', 'Single'); INSERT INTO product VALUES ('5', 'M9 SD', 0, 75, 15, 'Silenced', 'Single'); +INSERT INTO encrypted_data VALUES('1', '1c93722e6cf53f93dd4eb15a18444dc3e910fded18239db612794059af1fa5e8'); + -- -- Data for Name: reservation; Type: TABLE DATA; Schema: strongloop; Owner: strongloop From 827354a5a886654a177135eb0cb94e1b8769790c Mon Sep 17 00:00:00 2001 From: SAMI BETTAYEB Date: Tue, 22 Jun 2021 21:53:08 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20=E2=9C=85=20Add=20encrypted=20d?= =?UTF-8?q?ata=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SAMI BETTAYEB --- test/postgresql.encrypted.test.js | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/postgresql.encrypted.test.js diff --git a/test/postgresql.encrypted.test.js b/test/postgresql.encrypted.test.js new file mode 100644 index 00000000..4dd7e5cd --- /dev/null +++ b/test/postgresql.encrypted.test.js @@ -0,0 +1,79 @@ +// Copyright IBM Corp. 2014,2019. All Rights Reserved. +// Node module: loopback-connector-postgresql +// This file is licensed under the Artistic License 2.0. +// License text available at https://opensource.org/licenses/Artistic-2.0 + +'use strict'; +process.env.NODE_ENV = 'test'; +require('should'); +const expect = require('chai').expect; +const async = require('async'); +const chai = require('chai'); +const chaiSubset = require('chai-subset'); +chai.use(chaiSubset); + +let db; + +before(function() { + db = global.getSchema(); +}); + +describe('Mapping models', function() { + it('should return encrypted data by filter', function(done) { + const schema = + { + 'name': 'EncryptedData', + 'options': { + 'idInjection': false, + 'postgresql': { + 'schema': 'public', 'table': 'encrypted_data', + }, + }, + 'properties': { + 'id': { + 'type': 'String', + 'id': true, + }, + 'data': { + 'type': 'String', + }, + }, + 'mixins': { + 'Encryption': { + 'fields': [ + 'data', + ], + }, + }, + }; + + const EncryptedData = db.createModel(schema.name, schema.properties, schema.options); + EncryptedData.settings.mixins = schema.mixins; + + db.automigrate('EncryptedData', function(err) { + if (err) console.error({err}); + EncryptedData.create({ + id: '2', + data: '1c93722e6cf53f93dd4eb15a18444dc3e910fded18239db612794059af1fa5e8', + }, function(err, encryptedData) { + if (err) console.log({err2: err}); + async.series([ + function(callback) { + EncryptedData.findOne({where: {data: {ilike: '%test%'}}}, function(err, retreivedData) { + if (err) console.error({err111: err}); + expect(retreivedData).to.containSubset(encryptedData); + callback(null, retreivedData); + }); + }, + function(callback) { + EncryptedData.find({where: {data: {ilike: '%not found%'}}}, function(err, retreivedData) { + if (err) console.error({err111: err}); + expect(retreivedData.length).to.equal(0); + callback(null, retreivedData); + }); + }, + ], done); + }); + }); + }); +}); From af6500b4d3293989e61120161ff91dbf4c6e9292 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Sun, 12 Sep 2021 23:09:10 +0800 Subject: [PATCH 6/6] chore: fix package lock Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- package-lock.json | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5f2b6d03..39d2199e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "loopback-connector-postgresql", "version": "5.4.0", "license": "Artistic-2.0", "dependencies": { @@ -19,6 +20,8 @@ }, "devDependencies": { "@commitlint/config-conventional": "^12.1.4", + "chai": "^4.3.4", + "chai-subset": "^1.6.0", "eslint": "^7.7.0", "eslint-config-loopback": "^13.1.0", "juggler-v3": "file:./deps/juggler-v3", @@ -730,6 +733,15 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -870,6 +882,32 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-subset": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz", + "integrity": "sha1-pdDKFOMpp5WW7XAFi2ZGvWmIz+k=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -913,6 +951,15 @@ "node": "*" } }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -1060,6 +1107,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1545,6 +1604,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -2517,6 +2585,15 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pg": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz", @@ -3760,6 +3837,12 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -3873,6 +3956,26 @@ "upper-case-first": "^2.0.2" } }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chai-subset": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz", + "integrity": "sha1-pdDKFOMpp5WW7XAFi2ZGvWmIz+k=", + "dev": true + }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -3907,6 +4010,12 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -4023,6 +4132,15 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -4394,6 +4512,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -5454,6 +5578,12 @@ } } }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pg": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",