From df9c1e40992eca04c373ae2668712ed096754d80 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 28 Jan 2020 12:13:27 +0530 Subject: [PATCH 1/7] Github issue#3527, Reporting POC - Initial draft, working solution for generating embed URL --- config/custom-environment-variables.json | 2 + config/default.json | 2 + package-lock.json | 127 ++++++++++-------- src/permissions/index.js | 3 + src/routes/index.js | 4 + src/routes/projectReports/getEmbedReport.js | 59 +++++++++ src/services/lookerService.js | 136 ++++++++++++++++++++ 7 files changed, 278 insertions(+), 55 deletions(-) create mode 100644 src/routes/projectReports/getEmbedReport.js create mode 100644 src/services/lookerService.js diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index b927d972..e1131654 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -55,6 +55,8 @@ "pageSize": "PAGE_SIZE", "SSO_REFCODES": "SSO_REFCODES", "lookerConfig": { + "EMBED_KEY": "LOOKER_EMBED_KEY", + "LOOKER_HOST": "LOOKER_HOST", "BASE_URL": "LOOKER_API_BASE_URL", "CLIENT_ID": "LOOKER_API_CLIENT_ID", "CLIENT_SECRET": "LOOKER_API_CLIENT_SECRET", diff --git a/config/default.json b/config/default.json index ccca74c6..e09fa878 100644 --- a/config/default.json +++ b/config/default.json @@ -60,6 +60,8 @@ "VALID_STATUSES_BEFORE_PAUSED": "[\"active\"]", "SSO_REFCODES": "[]", "lookerConfig": { + "EMBED_KEY": "FAKE_KEY", + "LOOKER_HOST": "demo.looker.com", "BASE_URL": "", "CLIENT_ID": "", "CLIENT_SECRET": "", diff --git a/package-lock.json b/package-lock.json index 586f817e..1185cad7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1495,7 +1495,7 @@ }, "babel-runtime": { "version": "6.6.1", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", + "resolved": "http://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", "requires": { "core-js": "^2.1.0" @@ -2191,7 +2191,7 @@ "dependencies": { "semver": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.0.1.tgz", "integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk=" } } @@ -2541,8 +2541,7 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "default-require-extensions": { "version": "1.0.0", @@ -2897,6 +2896,26 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "escodegen": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.13.0.tgz", + "integrity": "sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, "escope": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", @@ -3062,8 +3081,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.0.1", @@ -3086,14 +3104,12 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -3312,8 +3328,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "figures": { "version": "1.7.0", @@ -3580,8 +3595,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3602,14 +3616,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3624,20 +3636,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3754,8 +3763,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3767,7 +3775,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3782,7 +3789,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3790,14 +3796,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3816,7 +3820,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3897,8 +3900,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3910,7 +3912,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3996,8 +3997,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4033,7 +4033,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4053,7 +4052,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4097,14 +4095,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4186,7 +4182,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, - "optional": true, "requires": { "is-glob": "^2.0.0" } @@ -4679,8 +4674,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "optional": true + "dev": true }, "is-finite": { "version": "1.0.2", @@ -4711,7 +4705,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -5216,6 +5209,28 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, + "jsonpath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.2.tgz", + "integrity": "sha512-rmzlgFZiQPc6q4HDyK8s9Qb4oxBnI5sF61y/Co5PV0lc3q2bIuRsNdueVbhoSHdKM4fxeimphOAtfz47yjCfeA==", + "requires": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.7.0" + }, + "dependencies": { + "esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=" + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + } + } + }, "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", @@ -5349,7 +5364,7 @@ }, "semver": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.1.0.tgz", "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" } } @@ -5358,7 +5373,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -6455,7 +6469,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, - "optional": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -6662,7 +6675,6 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -6993,8 +7005,7 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prepend-http": { "version": "1.0.4", @@ -8321,6 +8332,14 @@ "tweetnacl": "~0.14.0" } }, + "static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "requires": { + "escodegen": "^1.8.1" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -8743,7 +8762,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, "requires": { "prelude-ls": "~1.1.2" } @@ -9217,8 +9235,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "wordwrap": { "version": "1.0.0", diff --git a/src/permissions/index.js b/src/permissions/index.js index f9c6fc96..55c4367b 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -139,4 +139,7 @@ module.exports = () => { // Project Estimation Items Authorizer.setPolicy('projectEstimation.item.list', copilotAndAbove); + + // Project Reporting + Authorizer.setPolicy('projectReporting.managers', connectManagerOrAdmin); }; diff --git a/src/routes/index.js b/src/routes/index.js index 275ce470..361e4738 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -339,6 +339,10 @@ router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/work .patch(require('./workItems/update')) .delete(require('./workItems/delete')); + +router.route('/v5/projects/:projectId/reports/embed') +.get(require('./projectReports/getEmbedReport')); + router.route('/v5/projects/:projectId/reports') .get(require('./projectReports/getReport')); diff --git a/src/routes/projectReports/getEmbedReport.js b/src/routes/projectReports/getEmbedReport.js new file mode 100644 index 00000000..a28ce61a --- /dev/null +++ b/src/routes/projectReports/getEmbedReport.js @@ -0,0 +1,59 @@ +/* eslint-disable no-unused-vars */ +// import config from 'config'; +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import { PROJECT_MEMBER_MANAGER_ROLES, USER_ROLE, PROJECT_MEMBER_ROLE } from '../../constants'; +import lookerSerivce from '../../services/lookerService'; + +const permissions = tcMiddleware.permissions; + + +module.exports = [ + permissions('projectReporting.managers'), + async (req, res, next) => { + const projectId = Number(req.params.projectId); + const reportName = req.query.reportName; + const authUser = req.authUser; + + try { + // check if auth user has acecss to this project + const members = req.context.currentProjectMembers; + let member = _.find(members, m => m.userId === req.authUser.userId); + const isManager = member && PROJECT_MEMBER_MANAGER_ROLES.indexOf(member.role) > -1; + const isAdmin = util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]); + const isCopilot = member && member.role === PROJECT_MEMBER_ROLE.COPILOT; + const isCustomer = member && member.role === PROJECT_MEMBER_ROLE.CUSTOMER; + console.log(isAdmin, 'isAdmin'); + console.log(member, 'member'); + if (!member && isAdmin) { + const token = await util.getM2MToken(); + console.log(token); + const adminUser = await util.getTopcoderUser(authUser.userId, token, req.log); + console.log(adminUser, 'adminUser'); + member = { + firstName: adminUser.firstName, + lastName: adminUser.lastName, + userId: adminUser.userId, + role: '', + }; + } + // pick the report based on its name + let result = {}; + const project = { id: projectId }; + switch (reportName) { + case 'summary': + result = await lookerSerivce.generateEmbedUrl(req.authUser, project, member, '/embed/looks/1'); + break; + default: + return res.status(404).send('Report not found'); + } + + req.log.debug(result); + return res.status(200).json(result); + } catch (err) { + req.log.error(err); + return res.status(500).send(err.toString()); + } + }, +]; diff --git a/src/services/lookerService.js b/src/services/lookerService.js new file mode 100644 index 00000000..20017b8e --- /dev/null +++ b/src/services/lookerService.js @@ -0,0 +1,136 @@ +import config from 'config'; +// import _ from 'lodash'; +import crypto from 'crypto'; +import querystring from 'querystring'; + +/** + * Generates a random Nonce + * + * @param {Integer} len required length of a random nonce + * @returns {String} a unique string of given length, to be used as nonce + */ +function nonce(len) { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for (let i = 0; i < len; i += 1) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + + return text; +} + +/** + * Encodes the given string in UNICODE + * + * @param {String} string String to be encoded + * @returns {String} unicode encoded string + */ +function forceUnicodeEncoding(string) { + return decodeURIComponent(encodeURIComponent(string)); +} + +/** + * Creates signed embed URL for looker + * + * @param {Array} options array of configurable options + * @returns {String} unique signed URL, it can be GET only once + */ +function createdSignedEmbedUrl(options) { + // looker options + const secret = options.secret; + const host = options.host; + + // user options + const jsonExternalUserId = JSON.stringify(options.external_user_id.toString()); + const jsonFirstName = JSON.stringify(options.first_name); + const jsonLastName = JSON.stringify(options.last_name); + const jsonPermissions = JSON.stringify(options.permissions); + const jsonModels = JSON.stringify(options.models); + const jsonGroupIds = JSON.stringify(options.group_ids); + const jsonExternalGroupId = JSON.stringify(options.external_group_id || ''); + const jsonUserAttributes = JSON.stringify(options.user_attributes || {}); + const jsonAccessFilters = JSON.stringify(options.access_filters); + + // url/session specific options + const embedPath = `/login/embed/${encodeURIComponent(options.embed_url)}`; + const jsonSessionLength = JSON.stringify(options.session_length); + const jsonForceLogoutLogin = JSON.stringify(options.force_logout_login); + + // computed options + const jsonTime = JSON.stringify(Math.floor((new Date()).getTime() / 1000)); + const jsonNonce = JSON.stringify(nonce(16)); + + // compute signature + const stringToSign = `${host}\n${embedPath}\n${jsonNonce}\n${jsonTime}\n${jsonSessionLength}\n${jsonExternalUserId}\n${jsonPermissions}\n${jsonModels}\n${jsonGroupIds}\n${jsonExternalGroupId}\n${jsonUserAttributes}\n${jsonAccessFilters}`;// eslint-disable-line max-len + + const signature = crypto + .createHmac('sha1', secret) + .update(forceUnicodeEncoding(stringToSign)) + .digest('base64').trim(); + + // construct query string + const queryParams = { + nonce: jsonNonce, + time: jsonTime, + session_length: jsonSessionLength, + external_user_id: jsonExternalUserId, + permissions: jsonPermissions, + models: jsonModels, + access_filters: jsonAccessFilters, + first_name: jsonFirstName, + last_name: jsonLastName, + group_ids: jsonGroupIds, + external_group_id: jsonExternalGroupId, + user_attributes: jsonUserAttributes, + force_logout_login: jsonForceLogoutLogin, + signature, + }; + + const queryString = querystring.stringify(queryParams); + + return `${host}${embedPath}?${queryString}`; +} + +/** + * Generates the looker embed URL for the given look/dashboard. + * + * @param {Object} authUser requesting user + * @param {Object} project project for which report URL is to be generated + * @param {Object} member member object for the requesting user + * @param {String} reportUrl embed URL (look,dashboard etc) + * @returns {String} URL for embedding the looker report, it can be GET only once + */ +function generateEmbedUrl(authUser, project, member, reportUrl) { + const FIFTEEN_MINUTES = 15 * 60; + const urlData = { + host: config.lookerConfig.LOOKER_HOST, + secret: config.lookerConfig.EMBED_KEY, + external_user_id: authUser.userId, + group_ids: [], + first_name: member.firstName, + last_name: member.lastName, + permissions: ['access_data', 'see_looks', 'see_user_dashboards'], + models: ['projects_tc_employees'], + access_filters: { + projects_tc_employees: { + connect_project_id: `${project.id}`, + }, + }, + user_attributes: { + connect_project_id: `${project.id}`, + user_roles_project: member.role, + user_roles_platform: authUser.roles, + }, + session_length: FIFTEEN_MINUTES, + embed_url: reportUrl, + force_logout_login: true, + }; + + const url = createdSignedEmbedUrl(urlData); + return `https://${url}`; +} + +module.exports = { + generateEmbedUrl, +}; From f476d6f14ee386e0756e8d3a644fc01443406d22 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 28 Jan 2020 12:14:08 +0530 Subject: [PATCH 2/7] Making the feature branch deployable temporarily --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 02e01d86..3fd32c1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,7 +109,7 @@ workflows: - test filters: branches: - only: ['develop'] + only: ['develop', 'feature/looker_reporting'] - deployProd: context : org-global requires: From 92edc2c88c33a23bbb0ffe0c287b68dc58f529bf Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 28 Jan 2020 12:52:36 +0530 Subject: [PATCH 3/7] Temporarily reducing the permission policy --- src/permissions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/permissions/index.js b/src/permissions/index.js index 55c4367b..b4f03ccb 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -141,5 +141,5 @@ module.exports = () => { Authorizer.setPolicy('projectEstimation.item.list', copilotAndAbove); // Project Reporting - Authorizer.setPolicy('projectReporting.managers', connectManagerOrAdmin); + Authorizer.setPolicy('projectReporting.managers', copilotAndAbove); }; From a6648ab9c9c33efc2387861e03d809cd7c94580d Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 28 Jan 2020 16:31:19 +0530 Subject: [PATCH 4/7] Increased session length --- src/services/lookerService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/lookerService.js b/src/services/lookerService.js index 20017b8e..7488b667 100644 --- a/src/services/lookerService.js +++ b/src/services/lookerService.js @@ -102,7 +102,7 @@ function createdSignedEmbedUrl(options) { * @returns {String} URL for embedding the looker report, it can be GET only once */ function generateEmbedUrl(authUser, project, member, reportUrl) { - const FIFTEEN_MINUTES = 15 * 60; + const SESSION_LENGTH = 86400; const urlData = { host: config.lookerConfig.LOOKER_HOST, secret: config.lookerConfig.EMBED_KEY, @@ -122,7 +122,7 @@ function generateEmbedUrl(authUser, project, member, reportUrl) { user_roles_project: member.role, user_roles_platform: authUser.roles, }, - session_length: FIFTEEN_MINUTES, + session_length: SESSION_LENGTH, embed_url: reportUrl, force_logout_login: true, }; From 041d4f9f2307d3dc59f5646e8de959e8c4824500 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 29 Jan 2020 14:14:44 +0530 Subject: [PATCH 5/7] Added project members to the request context for managerOrAdmin permission policy to allow reporting routes to use the members Removed console log statements --- src/permissions/connectManagerOrAdmin.ops.js | 11 ++++++++++- src/permissions/index.js | 2 +- src/routes/projectReports/getEmbedReport.js | 10 ++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/permissions/connectManagerOrAdmin.ops.js b/src/permissions/connectManagerOrAdmin.ops.js index 0a5fb15a..bf158c5d 100644 --- a/src/permissions/connectManagerOrAdmin.ops.js +++ b/src/permissions/connectManagerOrAdmin.ops.js @@ -1,5 +1,7 @@ +import _ from 'lodash'; import util from '../util'; import { MANAGER_ROLES } from '../constants'; +import models from '../models'; /** @@ -7,12 +9,19 @@ import { MANAGER_ROLES } from '../constants'; * @param {Object} req the express request instance * @return {Promise} returns a promise */ -module.exports = req => new Promise((resolve, reject) => { +module.exports = req => new Promise(async (resolve, reject) => { const hasAccess = util.hasRoles(req, MANAGER_ROLES); if (!hasAccess) { return reject(new Error('You do not have permissions to perform this action')); } + let projectId = req.params.projectId; + if (projectId) { + projectId = _.parseInt(projectId); + const members = await models.ProjectMember.getActiveProjectMembers(projectId); + req.context = req.context || {}; + req.context.currentProjectMembers = members; + } return resolve(true); }); diff --git a/src/permissions/index.js b/src/permissions/index.js index b4f03ccb..55c4367b 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -141,5 +141,5 @@ module.exports = () => { Authorizer.setPolicy('projectEstimation.item.list', copilotAndAbove); // Project Reporting - Authorizer.setPolicy('projectReporting.managers', copilotAndAbove); + Authorizer.setPolicy('projectReporting.managers', connectManagerOrAdmin); }; diff --git a/src/routes/projectReports/getEmbedReport.js b/src/routes/projectReports/getEmbedReport.js index a28ce61a..d1635fba 100644 --- a/src/routes/projectReports/getEmbedReport.js +++ b/src/routes/projectReports/getEmbedReport.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; -import { PROJECT_MEMBER_MANAGER_ROLES, USER_ROLE, PROJECT_MEMBER_ROLE } from '../../constants'; +import { USER_ROLE } from '../../constants'; import lookerSerivce from '../../services/lookerService'; const permissions = tcMiddleware.permissions; @@ -20,17 +20,11 @@ module.exports = [ // check if auth user has acecss to this project const members = req.context.currentProjectMembers; let member = _.find(members, m => m.userId === req.authUser.userId); - const isManager = member && PROJECT_MEMBER_MANAGER_ROLES.indexOf(member.role) > -1; const isAdmin = util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]); - const isCopilot = member && member.role === PROJECT_MEMBER_ROLE.COPILOT; - const isCustomer = member && member.role === PROJECT_MEMBER_ROLE.CUSTOMER; - console.log(isAdmin, 'isAdmin'); - console.log(member, 'member'); if (!member && isAdmin) { const token = await util.getM2MToken(); - console.log(token); const adminUser = await util.getTopcoderUser(authUser.userId, token, req.log); - console.log(adminUser, 'adminUser'); + req.log.debug(adminUser, 'adminUser'); member = { firstName: adminUser.firstName, lastName: adminUser.lastName, From c417dc4659f0f609f1255281b767ab933f783e92 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 29 Jan 2020 14:15:30 +0530 Subject: [PATCH 6/7] Removing feature branch from deployable branch list --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fd32c1e..02e01d86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,7 +109,7 @@ workflows: - test filters: branches: - only: ['develop', 'feature/looker_reporting'] + only: ['develop'] - deployProd: context : org-global requires: From 335ede5339506d9c13ee247490747ca22f36bdf2 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 29 Jan 2020 14:27:12 +0530 Subject: [PATCH 7/7] Configurable session length, via env variables. --- config/custom-environment-variables.json | 1 + config/default.json | 1 + src/services/lookerService.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 95f6a99c..a64f5869 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -58,6 +58,7 @@ "lookerConfig": { "EMBED_KEY": "LOOKER_EMBED_KEY", "LOOKER_HOST": "LOOKER_HOST", + "SESSION_LENGTH": "SESSION_LENGTH", "BASE_URL": "LOOKER_API_BASE_URL", "CLIENT_ID": "LOOKER_API_CLIENT_ID", "CLIENT_SECRET": "LOOKER_API_CLIENT_SECRET", diff --git a/config/default.json b/config/default.json index 62b19a4f..08e6f3f9 100644 --- a/config/default.json +++ b/config/default.json @@ -63,6 +63,7 @@ "lookerConfig": { "EMBED_KEY": "FAKE_KEY", "LOOKER_HOST": "demo.looker.com", + "SESSION_LENGTH": 86400, "BASE_URL": "", "CLIENT_ID": "", "CLIENT_SECRET": "", diff --git a/src/services/lookerService.js b/src/services/lookerService.js index 7488b667..33530cb0 100644 --- a/src/services/lookerService.js +++ b/src/services/lookerService.js @@ -102,7 +102,7 @@ function createdSignedEmbedUrl(options) { * @returns {String} URL for embedding the looker report, it can be GET only once */ function generateEmbedUrl(authUser, project, member, reportUrl) { - const SESSION_LENGTH = 86400; + const SESSION_LENGTH = config.lookerConfig.SESSION_LENGTH; const urlData = { host: config.lookerConfig.LOOKER_HOST, secret: config.lookerConfig.EMBED_KEY,