From b8caf229365d3e7c44325812a5db418162fbe956 Mon Sep 17 00:00:00 2001
From: Krishna Bottla <40598480+kbottla@users.noreply.github.com>
Date: Mon, 15 Jan 2024 21:10:24 +0000
Subject: [PATCH] PP-12058 View services as a user
---
.../browse-as-a-user.controller.js | 45 ++++++++++++
.../my-services/get-index.controller.js | 15 +++-
.../my-services/post-index.controller.js | 2 +-
...-service-and-gateway-account.middleware.js | 31 +++++---
app/middleware/user-is-authorised.js | 19 ++---
app/models/User.class.js | 20 +++++-
app/paths.js | 4 ++
app/routes.js | 6 ++
app/services/clients/adminusers.client.js | 42 +++++++++++
app/services/user.service.js | 8 +++
app/utils/display-converter.js | 9 ++-
app/views/browse-as-a-user/index.njk | 33 +++++++++
app/views/includes/browser-as-a-user-info.njk | 8 +++
app/views/layout.njk | 1 +
app/views/services/index.njk | 71 +++++++++++++------
15 files changed, 266 insertions(+), 48 deletions(-)
create mode 100644 app/controllers/browser-as-a-user/browse-as-a-user.controller.js
create mode 100644 app/views/browse-as-a-user/index.njk
create mode 100644 app/views/includes/browser-as-a-user-info.njk
diff --git a/app/controllers/browser-as-a-user/browse-as-a-user.controller.js b/app/controllers/browser-as-a-user/browse-as-a-user.controller.js
new file mode 100644
index 0000000000..9d42c7f526
--- /dev/null
+++ b/app/controllers/browser-as-a-user/browse-as-a-user.controller.js
@@ -0,0 +1,45 @@
+'use strict'
+
+const { response } = require('../../utils/response')
+const paths = require('../../paths')
+const userService = require('../../services/user.service')
+const { RESTClientError } = require('../../errors')
+
+function get (req, res) {
+ return response(req, res, 'browse-as-a-user/index')
+}
+
+async function post (req, res, next) {
+ const { body } = req
+
+ const emailAddress = body.emailAddress
+
+ try {
+ const userFound = await userService.findUserByEmail(emailAddress)
+ req.session.assumedUserId = userFound.externalId
+ req.session.assumedUserEmail = userFound.email
+ req.flash('generic', `You are now viewing ${userFound.email} services`)
+ return res.redirect(paths.serviceSwitcher.index)
+ } catch (error) {
+ if (error instanceof RESTClientError && error.errorCode === 404) {
+ req.flash('genericError', `User with email ${emailAddress} not found`)
+ }
+ else {
+ req.flash('genericError', `Something has gone wrong`)
+ }
+ return response(req, res, 'browse-as-a-user/index', { emailAddress: emailAddress })
+ }
+}
+
+function clear (req, res) {
+ delete req.session.assumedUserId
+ delete req.session.assumedUserEmail
+
+ res.redirect(paths.serviceSwitcher.index)
+}
+
+module.exports = {
+ get,
+ post,
+ clear
+}
diff --git a/app/controllers/my-services/get-index.controller.js b/app/controllers/my-services/get-index.controller.js
index 81e990dc6f..f45617f0ba 100644
--- a/app/controllers/my-services/get-index.controller.js
+++ b/app/controllers/my-services/get-index.controller.js
@@ -7,6 +7,8 @@ const serviceService = require('../../services/service.service')
const { filterGatewayAccountIds } = require('../../utils/permissions')
const getHeldPermissions = require('../../utils/get-held-permissions')
const { DEFAULT_SERVICE_NAME } = require('../../utils/constants')
+const getAdminUsersClient = require('../../services/clients/adminusers.client')
+const adminUsersClient = getAdminUsersClient()
function hasStripeAccount (gatewayAccounts) {
return gatewayAccounts.some(gatewayAccount =>
@@ -28,7 +30,14 @@ function sortServicesByLiveThenName (a, b) {
}
module.exports = async function getServiceList (req, res) {
- const servicesRoles = lodash.get(req, 'user.serviceRoles', [])
+ let servicesRoles
+ if (req.session.assumedUserId) {
+ const assumedUser = await adminUsersClient.getUserByExternalId(req.session.assumedUserId)
+ servicesRoles = lodash.get(assumedUser, 'serviceRoles', [])
+ } else {
+ servicesRoles = lodash.get(req, 'user.serviceRoles', [])
+ }
+
const newServiceId = res.locals.flash && res.locals.flash.inviteSuccessServiceId &&
res.locals.flash.inviteSuccessServiceId[0]
@@ -62,7 +71,9 @@ module.exports = async function getServiceList (req, res) {
services_singular: servicesData.length === 1,
env: process.env,
has_account_with_payouts: hasStripeAccount(aggregatedGatewayAccounts),
- has_live_account: filterGatewayAccountIds(aggregatedGatewayAccounts, true).length
+ has_live_account: filterGatewayAccountIds(aggregatedGatewayAccounts, true).length,
+ has_global_role: req.user.hasGlobalRole(),
+ assumedUserEmail: req.session.assumedUserEmail
}
if (newServiceId) {
diff --git a/app/controllers/my-services/post-index.controller.js b/app/controllers/my-services/post-index.controller.js
index 29b44590b2..d58b6e90c4 100644
--- a/app/controllers/my-services/post-index.controller.js
+++ b/app/controllers/my-services/post-index.controller.js
@@ -13,7 +13,7 @@ module.exports = (req, res) => {
const gatewayAccountId = req.body && req.body.gatewayAccountId
const gatewayAccountExternalId = req.body && req.body.gatewayAccountExternalId
- if (validAccountId(gatewayAccountId, req.user)) {
+ if (validAccountId(gatewayAccountId, req.user) || req.user.hasGlobalRole()) {
res.redirect(302, formatAccountPathsFor(paths.account.dashboard.index, gatewayAccountExternalId))
} else {
logger.warn(`Attempted to switch to invalid account ${gatewayAccountId}`)
diff --git a/app/middleware/get-service-and-gateway-account.middleware.js b/app/middleware/get-service-and-gateway-account.middleware.js
index e1ab8ec788..07f135ab0b 100644
--- a/app/middleware/get-service-and-gateway-account.middleware.js
+++ b/app/middleware/get-service-and-gateway-account.middleware.js
@@ -10,6 +10,8 @@ const { keys } = require('@govuk-pay/pay-js-commons').logging
const { addField } = require('../utils/request-context')
const { getSwitchingCredentialIfExists } = require('../utils/credentials')
const connectorClient = new Connector(process.env.CONNECTOR_URL)
+const getAdminUsersClient = require('../services/clients/adminusers.client')
+const adminUsersClient = getAdminUsersClient()
async function getGatewayAccountByExternalId (gatewayAccountExternalId) {
try {
@@ -47,21 +49,29 @@ async function getGatewayAccountByExternalId (gatewayAccountExternalId) {
}
}
-function getService (user, serviceExternalId, gatewayAccount) {
+async function getService (user, serviceExternalId, gatewayAccount) {
let service
const serviceRoles = _.get(user, 'serviceRoles', [])
- if (serviceRoles.length > 0) {
+ if (user.role) {
if (serviceExternalId) {
- service = _.get(serviceRoles.find(serviceRole => {
- return (serviceRole.service.externalId === serviceExternalId &&
- (!gatewayAccount || serviceRole.service.gatewayAccountIds.includes(String(gatewayAccount.gateway_account_id))))
- }), 'service')
- } else {
- if (gatewayAccount) {
+ service = await adminUsersClient.findServiceByExternalId(serviceExternalId)
+ } else if (gatewayAccount) {
+ service = await adminUsersClient.findServiceByExternalId(gatewayAccount.service_id)
+ }
+ } else {
+ if (serviceRoles.length > 0) {
+ if (serviceExternalId) {
service = _.get(serviceRoles.find(serviceRole => {
- return serviceRole.service.gatewayAccountIds.includes(String(gatewayAccount.gateway_account_id))
+ return (serviceRole.service.externalId === serviceExternalId &&
+ (!gatewayAccount || serviceRole.service.gatewayAccountIds.includes(String(gatewayAccount.gateway_account_id))))
}), 'service')
+ } else {
+ if (gatewayAccount) {
+ service = _.get(serviceRoles.find(serviceRole => {
+ return serviceRole.service.gatewayAccountIds.includes(String(gatewayAccount.gateway_account_id))
+ }), 'service')
+ }
}
}
}
@@ -98,12 +108,11 @@ module.exports = async function getServiceAndGatewayAccount (req, res, next) {
// uses req.user object which is set by passport (auth.service.js) and has all user services information to find service by serviceExternalId or gatewayAccountId.
// A separate API call to adminusers to find service makes it independent of user object but most of tests setup currently relies on req.user
- const service = getService(req.user, serviceExternalId, gatewayAccount)
+ const service = await getService(req.user, serviceExternalId, gatewayAccount)
if (service) {
req.service = service
addField(keys.SERVICE_EXTERNAL_ID, service.externalId)
}
-
if (environment) {
req.isLive = environment === 'live'
}
diff --git a/app/middleware/user-is-authorised.js b/app/middleware/user-is-authorised.js
index 74fcf8da9e..f1b7663308 100644
--- a/app/middleware/user-is-authorised.js
+++ b/app/middleware/user-is-authorised.js
@@ -22,14 +22,17 @@ module.exports = function userIsAuthorised (req, res, next) {
return next(new UserAccountDisabledError('User account is disabled'))
}
- if (params[GATEWAY_ACCOUNT_EXTERNAL_ID] || params[SERVICE_EXTERNAL_ID]) {
- if (!service) {
- return next(new NotAuthorisedError('Service not found on request'))
- }
- if (!user.serviceRoles.find(serviceRole => serviceRole.service.externalId === service.externalId)) {
- return next(new NotAuthorisedError('User does not have service role for service'))
+ if (user.role && user.role.name != null) {
+ next()
+ } else {
+ if (params[GATEWAY_ACCOUNT_EXTERNAL_ID] || params[SERVICE_EXTERNAL_ID]) {
+ if (!service) {
+ return next(new NotAuthorisedError('Service not found on request'))
+ }
+ if (!user.serviceRoles.find(serviceRole => serviceRole.service.externalId === service.externalId)) {
+ return next(new NotAuthorisedError('User does not have service role for service'))
+ }
}
+ next()
}
-
- next()
}
diff --git a/app/models/User.class.js b/app/models/User.class.js
index e0837b574e..892566b940 100644
--- a/app/models/User.class.js
+++ b/app/models/User.class.js
@@ -39,6 +39,7 @@ class User {
}
this.externalId = userData.external_id
this.email = userData.email || ''
+ this.role = userData.role || ''
this.serviceRoles = userData.service_roles.map(serviceRoleData => new ServiceRole(serviceRoleData))
this.otpKey = userData.otp_key || ''
this.telephoneNumber = userData.telephone_number || ''
@@ -94,9 +95,16 @@ class User {
* @returns {boolean} Whether or not the user has the given permission
*/
hasPermission (serviceExternalId, permissionName) {
- return _.get(this.getRoleForService(serviceExternalId), 'permissions', [])
+ let hasPermission = _.get(this.getRoleForService(serviceExternalId), 'permissions', [])
.map(permission => permission.name)
.includes(permissionName)
+
+ if (!hasPermission && this.role) {
+ return _.get(this, 'role.permissions', [])
+ .map(permission => permission.name)
+ .includes(permissionName)
+ }
+ return hasPermission
}
/**
@@ -127,11 +135,19 @@ class User {
return _.get(this.getRoleForService(serviceExternalId), 'permissions', []).map(permission => permission.name)
}
+ hasGlobalRole () {
+ return _.get(this, 'role') !== undefined
+ }
+
+ getGlobalPermissions () {
+ return _.get(this, 'role.permissions', []).map(permission => permission.name)
+ }
+
isAdminUserForService (serviceExternalId) {
return this.serviceRoles
.filter(serviceRole => serviceRole.role && serviceRole.role.name === 'admin' &&
serviceRole.service && serviceRole.service.externalId === serviceExternalId)
- .length > 0
+ .length > 0 || (_.get(this, 'role.name') === 'admin')
}
}
diff --git a/app/paths.js b/app/paths.js
index 7a150b0e17..28291ed17e 100644
--- a/app/paths.js
+++ b/app/paths.js
@@ -230,6 +230,10 @@ module.exports = {
switch: '/my-services/switch',
create: '/my-services/create'
},
+ browseAsUser: {
+ index: '/browse-as-a-user',
+ clear: '/browse-as-a-user/clear'
+ },
invite: {
validateInvite: '/invites/:code',
subscribeService: '/subscribe'
diff --git a/app/routes.js b/app/routes.js
index 12ba9a48cf..c8d1425a75 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -40,6 +40,7 @@ const digitalWalletController = require('./controllers/digital-wallet')
const emailNotificationsController = require('./controllers/email-notifications/email-notifications.controller')
const forgotPasswordController = require('./controllers/forgotten-password.controller')
const myServicesController = require('./controllers/my-services')
+const browseAsAUserController = require('./controllers/browser-as-a-user/browse-as-a-user.controller')
const editServiceNameController = require('./controllers/edit-service-name/edit-service-name.controller')
const serviceUsersController = require('./controllers/service-users.controller')
const organisationDetailsController = require('./controllers/organisation-details.controller')
@@ -87,6 +88,7 @@ const agreementsController = require('./controllers/agreements/agreements.contro
const organisationUrlController = require('./controllers/switch-psp/organisation-url')
const registrationController = require('./controllers/registration/registration.controller')
const privacyController = require('./controllers/privacy/privacy.controller')
+const { browseAsUser } = require('./paths')
// Assignments
const {
@@ -218,6 +220,10 @@ module.exports.bind = function (app) {
// OUTSIDE OF SERVICE ROUTES
// -------------------------
+ app.get(browseAsUser.index, userIsAuthorised, browseAsAUserController.get)
+ app.post(browseAsUser.index, userIsAuthorised, browseAsAUserController.post)
+ app.get(browseAsUser.clear, userIsAuthorised, browseAsAUserController.clear)
+
// Service switcher
app.get(serviceSwitcher.index, userIsAuthorised, myServicesController.getIndex)
app.post(serviceSwitcher.switch, userIsAuthorised, myServicesController.postIndex)
diff --git a/app/services/clients/adminusers.client.js b/app/services/clients/adminusers.client.js
index 7b788228e0..54894df6d9 100644
--- a/app/services/clients/adminusers.client.js
+++ b/app/services/clients/adminusers.client.js
@@ -45,6 +45,28 @@ module.exports = function (clientOptions = {}) {
)
}
+ /**
+ * Get a User by email address
+ *
+ * @param {string} externalId
+ * @return {Promise
+
+ View transactions for all your services
+
+
-
- View transactions for all your services
+
+ View payments to your bank account
- {{ govukButton({
+ {% if not assumedUserEmail %}
+
+ {{ govukButton({
classes: "govuk-button--secondary",
text: "Add a new service",
href: routes.serviceSwitcher.create
}) }}
- Search user by email
+
+
+ Overview
Admin
+
+
+
+
+ Reports
+ Reports
- {% set allServiceTransactionsPath = routes.allServiceTransactions.index if has_live_account else
- routes.formattedPathFor(routes.allServiceTransactions.indexStatusFilter, 'test') %}
+ {% set allServiceTransactionsPath = routes.allServiceTransactions.index if has_live_account else
+ routes.formattedPathFor(routes.allServiceTransactions.indexStatusFilter, 'test') %}
+ Services
-
You do not have any services set up at the moment. Either - create a new one + create a new + one or contact an administrator of an existing service to be added to it.
- {% endif %} + {% endif %} {% endblock %}