From d6d402fa45a95d095b6e20562f21875278293783 Mon Sep 17 00:00:00 2001 From: gets0ul Date: Tue, 10 Mar 2020 14:34:10 +0700 Subject: [PATCH 1/6] Allow Account Manager to remove member. --- src/permissions/projectMember.delete.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/permissions/projectMember.delete.js b/src/permissions/projectMember.delete.js index 5f4bb946..eb0a7bc0 100644 --- a/src/permissions/projectMember.delete.js +++ b/src/permissions/projectMember.delete.js @@ -26,6 +26,7 @@ module.exports = freq => new Promise((resolve, reject) => { // check if auth user has acecss to this project const hasAccess = util.hasAdminRole(req) || (authMember && memberToBeRemoved && ([ + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, PROJECT_MEMBER_ROLE.PROJECT_MANAGER, From 84deeb76476c8e0edff31ecea497e96ac1262c09 Mon Sep 17 00:00:00 2001 From: gets0ul Date: Tue, 10 Mar 2020 16:05:33 +0700 Subject: [PATCH 2/6] Fix searching projects by handle to be case insensitive. --- src/routes/projects/list.spec.js | 98 +++++++++++++++++++++++++++++++- src/utils/es-config.js | 1 - 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 9ebb3e13..e570e225 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -160,7 +160,7 @@ const data = [ role: 'manager', firstName: 'first', lastName: 'last', - handle: 'manager_handle', + handle: 'MANAGER_HANDLE', isPrimary: true, createdBy: 1, updatedBy: 1, @@ -723,7 +723,7 @@ describe('LIST Project', () => { }); }); - it('should return all projects that match when filtering by customer handle', (done) => { + it('should return all projects that match when filtering by customer handle (lowercase)', (done) => { request(server) .get('/v5/projects/?customer=*tourist*') .set({ @@ -746,7 +746,53 @@ describe('LIST Project', () => { }); }); - it('should return all projects that match when filtering by manager handle', (done) => { + it('should return all projects that match when filtering by customer handle (uppercase)', (done) => { + request(server) + .get('/v5/projects/?customer=*TOUR*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test1'); + resJson[0].members.should.have.deep.property('[0].role', 'customer'); + resJson[0].members[0].userId.should.equal(40051331); + done(); + } + }); + }); + + it('should return all projects that match when filtering by customer handle (mixed case)', (done) => { + request(server) + .get('/v5/projects/?customer=*tOURiS*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test1'); + resJson[0].members.should.have.deep.property('[0].role', 'customer'); + resJson[0].members[0].userId.should.equal(40051331); + done(); + } + }); + }); + + it('should return all projects that match when filtering by manager handle (lowercase)', (done) => { request(server) .get('/v5/projects/?manager=*_handle') .set({ @@ -769,6 +815,52 @@ describe('LIST Project', () => { }); }); + it('should return all projects that match when filtering by manager handle (uppercase)', (done) => { + request(server) + .get('/v5/projects/?manager=MANAG*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test3'); + resJson[0].members.should.have.deep.property('[0].role', 'manager'); + resJson[0].members[0].userId.should.equal(40051334); + done(); + } + }); + }); + + it('should return all projects that match when filtering by manager handle (mixed case)', (done) => { + request(server) + .get('/v5/projects/?manager=*_HAndLe') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test3'); + resJson[0].members.should.have.deep.property('[0].role', 'manager'); + resJson[0].members[0].userId.should.equal(40051334); + done(); + } + }); + }); + it('should return all projects that match when filtering by manager, searching on any non-customer role', (done) => { request(server) .get('/v5/projects/?manager=copi*') diff --git a/src/utils/es-config.js b/src/utils/es-config.js index 657add4b..c8e1be8a 100644 --- a/src/utils/es-config.js +++ b/src/utils/es-config.js @@ -241,7 +241,6 @@ MAPPINGS[ES_PROJECT_INDEX] = { }, handle: { type: 'string', - index: 'not_analyzed', }, id: { type: 'long', From 12612e0e828cf391b680a1855a42eef32bc44ba5 Mon Sep 17 00:00:00 2001 From: tcchhabra Date: Wed, 11 Mar 2020 14:15:48 +0530 Subject: [PATCH 3/6] fix for issue #3740 --- src/routes/projects/list.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 8ca1279a..f31d2124 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -62,7 +62,18 @@ const SUPPORTED_FILTERS = [ const escapeEsKeyword = keyword => keyword.replace(/[+-=>|\<|\!|\(|\)|\{|\}|\[|\]|\^|"|~|\*|\?|\:|\\|\/)/g, '\\$&'); +} + const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { + keyword = escapeElasticsearchQuery(keyword); let should = [ { query_string: { From f8ecc6b0f08fe5e331eb0960f443663245914c65 Mon Sep 17 00:00:00 2001 From: tcchhabra Date: Wed, 11 Mar 2020 14:43:19 +0530 Subject: [PATCH 4/6] fix for issue #3740 --- src/routes/projects/list.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index f31d2124..a40fe370 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -69,15 +69,21 @@ const escapeEsKeyword = keyword => keyword.replace(/[+-=>|\<|\!|\(|\)|\{|\}|\[|\]|\^|"|~|\*|\?|\:|\\|\/)/g, '\\$&'); + const chars = ['\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', + '^', '"', '~', '*', '?', ':', '/', '<', '>']; + let result = query; + _.forEach(chars, (item) => { + result = result.replace(item, `\\${item}`); + }); + return result; } const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { - keyword = escapeElasticsearchQuery(keyword); + const escapedKeyword = escapeElasticsearchQuery(keyword); let should = [ { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD), fields: ['name^5', 'description^3', 'type^2'], }, @@ -90,7 +96,7 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { path: 'details.utm', query: { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD || matchType === MATCH_TYPE_SINGLE_FIELD), fields: ['details.utm.code^4'], }, @@ -104,7 +110,7 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { path: 'members', query: { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD), fields: ['members.email', 'members.handle', 'members.firstName', 'members.lastName'], }, From 049f4dc8277365c90c919f24ce18223b45128921 Mon Sep 17 00:00:00 2001 From: tcchhabra Date: Sat, 14 Mar 2020 12:01:16 +0530 Subject: [PATCH 5/6] fix for issue #3740 feedback --- src/routes/projects/list.js | 41 +++++++++++++++-------------- src/routes/projects/list.spec.js | 44 ++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index a40fe370..97402f40 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -62,28 +62,11 @@ const SUPPORTED_FILTERS = [ const escapeEsKeyword = keyword => keyword.replace(/[+-=>']; - let result = query; - _.forEach(chars, (item) => { - result = result.replace(item, `\\${item}`); - }); - return result; -} - const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { - const escapedKeyword = escapeElasticsearchQuery(keyword); let should = [ { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD), fields: ['name^5', 'description^3', 'type^2'], }, @@ -96,7 +79,7 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { path: 'details.utm', query: { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD || matchType === MATCH_TYPE_SINGLE_FIELD), fields: ['details.utm.code^4'], }, @@ -110,7 +93,7 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => { path: 'members', query: { query_string: { - query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? escapedKeyword : `*${escapedKeyword}*`, + query: (matchType === MATCH_TYPE_EXACT_PHRASE) ? keyword : `*${keyword}*`, analyze_wildcard: (matchType === MATCH_TYPE_WILDCARD), fields: ['members.email', 'members.handle', 'members.firstName', 'members.lastName'], }, @@ -285,6 +268,22 @@ const setFilter = (value, keyword, fieldName) => { return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_EXACT_PHRASE, fieldName); }; +/** + * ES need to skip special chars else it is considered as RegEx + * + * @param {String} query query being searched for + * @return {String} result after parsing + */ + function escapeElasticsearchQuery(query) { + const chars = ['\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', + '^', '"', '~', '*', '?', ':', '/', '<', '>']; + let result = query; + _.forEach(chars, (item) => { + result = result.replace(item, `\\${item}`); + }); + return result; + } + /** * Parse the ES search criteria and prepare search request body * @@ -443,7 +442,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { if (!keyword) { // Not a specific field search nor an exact phrase search, do a wildcard match - keyword = criteria.filters.keyword; + keyword = escapeElasticsearchQuery(keywordCriterion); matchType = MATCH_TYPE_WILDCARD; } diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 9ebb3e13..373f4dbb 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -21,7 +21,7 @@ const data = [ type: 'generic', billingAccountId: 1, name: 'test1', - description: 'test project1', + description: 'test project1 abc/d', status: 'active', details: { utm: { @@ -1065,7 +1065,7 @@ describe('LIST Project', () => { resJson.should.have.lengthOf(1); resJson[0].should.have.property('description'); resJson[0].should.not.have.property('cancelReason'); - resJson[0].description.should.be.eq('test project1'); + resJson[0].description.should.be.eq('test project1 abc/d'); done(); } }); @@ -1244,6 +1244,46 @@ describe('LIST Project', () => { } }); }); + + it('should find a project by quoted keyword with a special symbol in the name', (done) => { + request(server) + .get('/v5/projects/?keyword="abc/d"') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + done(); + } + }); + }); + + it('should find a project by keyword with a special symbol in the name', (done) => { + request(server) + .get('/v5/projects/?keyword=abc/d') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + done(); + } + }); + }); }); }); }); From 91b614273469758fcf967efc954ef40100a94220 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 16 Mar 2020 11:32:11 +0800 Subject: [PATCH 6/6] fix: searching by "ref" and reuse existent method reuse existent method "escapeEsKeyword" instead of new "escapeElasticsearchQuery" --- src/routes/projects/list.js | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 97402f40..e7ebf108 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -60,7 +60,14 @@ const SUPPORTED_FILTERS = [ 'directProjectId', ]; -const escapeEsKeyword = keyword => keyword.replace(/[+-=> keyword.replace(/[+-=> { let should = [ @@ -268,22 +275,6 @@ const setFilter = (value, keyword, fieldName) => { return buildEsQueryWithFilter(value, keyword, MATCH_TYPE_EXACT_PHRASE, fieldName); }; -/** - * ES need to skip special chars else it is considered as RegEx - * - * @param {String} query query being searched for - * @return {String} result after parsing - */ - function escapeElasticsearchQuery(query) { - const chars = ['\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', - '^', '"', '~', '*', '?', ':', '/', '<', '>']; - let result = query; - _.forEach(chars, (item) => { - result = result.replace(item, `\\${item}`); - }); - return result; - } - /** * Parse the ES search criteria and prepare search request body * @@ -442,7 +433,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { if (!keyword) { // Not a specific field search nor an exact phrase search, do a wildcard match - keyword = escapeElasticsearchQuery(keywordCriterion); + keyword = escapeEsKeyword(keywordCriterion); matchType = MATCH_TYPE_WILDCARD; }