From df44788034266abe3b5134956e89ed18ee0aec2b Mon Sep 17 00:00:00 2001 From: Sebastian Mihalache <sebastian.mihalache@gmail.con> Date: Thu, 28 Jun 2018 08:48:17 +0300 Subject: [PATCH] fix tests imports across all components --- .../src/fixtures/fragments.js | 3 + .../config/authsome-helpers.js | 131 --------- .../component-invite/config/authsome-mode.js | 264 +---------------- packages/component-invite/config/default.js | 71 +---- packages/component-invite/config/test.js | 72 +---- .../src/routes/fragmentsInvitations/get.js | 1 + .../config/authsome-helpers.js | 130 --------- .../config/authsome-mode.js | 268 +----------------- .../config/default.js | 91 +----- .../config/test.js | 92 +----- .../config/authsome-helpers.js | 131 --------- .../config/authsome-mode.js | 252 +--------------- .../component-user-manager/config/default.js | 91 +----- .../component-user-manager/config/test.js | 81 +----- 14 files changed, 20 insertions(+), 1658 deletions(-) delete mode 100644 packages/component-invite/config/authsome-helpers.js delete mode 100644 packages/component-manuscript-manager/config/authsome-helpers.js delete mode 100644 packages/component-user-manager/config/authsome-helpers.js diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index 2c43f94df..2ee8c78db 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -113,6 +113,7 @@ const fragments = { ], save: jest.fn(() => fragments.fragment), owners: [user.id], + type: 'fragment', }, newVersion: { id: chance.guid(), @@ -150,6 +151,7 @@ const fragments = { ], save: jest.fn(() => fragments.fragment), owners: [user.id], + type: 'fragment', }, noParentFragment: { id: chance.guid(), @@ -167,6 +169,7 @@ const fragments = { ], save: jest.fn(() => fragments.fragment), owners: [user.id], + type: 'fragment', }, } diff --git a/packages/component-invite/config/authsome-helpers.js b/packages/component-invite/config/authsome-helpers.js deleted file mode 100644 index 55148df34..000000000 --- a/packages/component-invite/config/authsome-helpers.js +++ /dev/null @@ -1,131 +0,0 @@ -const omit = require('lodash/omit') -const config = require('config') -const get = require('lodash/get') - -const statuses = config.get('statuses') - -const publicStatusesPermissions = ['author', 'reviewer'] - -const parseAuthorsData = (coll, matchingCollPerm) => { - if (['reviewer'].includes(matchingCollPerm.permission)) { - coll.authors = coll.authors.map(a => omit(a, ['email'])) - } -} - -const setPublicStatuses = (coll, matchingCollPerm) => { - const status = get(coll, 'status') || 'draft' - // coll.visibleStatus = statuses[status].public - if (publicStatusesPermissions.includes(matchingCollPerm.permission)) { - coll.visibleStatus = statuses[status].public - } -} - -const filterRefusedInvitations = (coll, user) => { - const matchingInv = coll.invitations.find(inv => inv.userId === user.id) - if (matchingInv === undefined) return null - if (matchingInv.hasAnswer === true && !matchingInv.isAccepted) return null - return coll -} - -const filterObjectData = ( - collectionsPermissions = [], - object = {}, - user = {}, -) => { - if (object.type === 'fragment') { - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.fragmentId, - ) - if (matchingCollPerm === undefined) return null - if (['reviewer'].includes(matchingCollPerm.permission)) { - object.files = omit(object.files, ['coverLetter']) - if (object.recommendations) - object.recommendations = object.recommendations.filter( - rec => rec.userId === user.id, - ) - } - parseAuthorsData(object, matchingCollPerm) - if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) { - return filterRefusedInvitations(object, user) - } - return object - } - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.id, - ) - if (matchingCollPerm === undefined) return null - setPublicStatuses(object, matchingCollPerm) - - return object -} - -const getTeamsByPermissions = async ( - teamIds = [], - permissions = [], - TeamModel, -) => - (await Promise.all( - teamIds.map(async teamId => { - const team = await TeamModel.find(teamId) - if (!permissions.includes(team.teamType.permissions)) { - return null - } - return team - }), - )).filter(Boolean) - -const heIsInvitedToFragment = async ({ user, Team, collectionId }) => - (await getTeamsByPermissions(user.teams, ['handlingEditor'], Team)).some( - // user is a member of the team with access to the fragment's parent collection - t => t.members.includes(user.id) && t.object.id === collectionId, - ) - -const getUserPermissions = async ({ - user, - Team, - mapFn = t => ({ - objectId: t.object.id, - objectType: t.object.type, - role: t.teamType.permissions, - }), -}) => - (await Promise.all(user.teams.map(teamId => Team.find(teamId)))).map(mapFn) - -const isOwner = ({ user: { id }, object }) => { - if (object.owners.includes(id)) return true - return !!object.owners.find(own => own.id === id) -} - -const hasPermissionForObject = async ({ user, object, Team, roles = [] }) => { - const userPermissions = await getUserPermissions({ - user, - Team, - }) - - return !!userPermissions.find(p => { - const hasObject = - p.objectId === get(object, 'fragment.id') || - p.objectId === get(object, 'fragment.collectionId') - if (roles.length > 0) { - return hasObject && roles.includes(p.role) - } - return hasObject - }) -} - -const isHandlingEditor = ({ user, object }) => - get(object, 'collection.handlingEditor.id') === user.id - -module.exports = { - filterObjectData, - parseAuthorsData, - setPublicStatuses, - getTeamsByPermissions, - filterRefusedInvitations, - // - isOwner, - isHandlingEditor, - getUserPermissions, - heIsInvitedToFragment, - hasPermissionForObject, -} diff --git a/packages/component-invite/config/authsome-mode.js b/packages/component-invite/config/authsome-mode.js index 8a92a1301..9c663beae 100644 --- a/packages/component-invite/config/authsome-mode.js +++ b/packages/component-invite/config/authsome-mode.js @@ -1,265 +1,3 @@ -const get = require('lodash/get') -const pickBy = require('lodash/pickBy') -const omit = require('lodash/omit') -const helpers = require('./authsome-helpers') - -async function teamPermissions(user, operation, object, context) { - const { models } = context - const permissions = ['handlingEditor', 'author', 'reviewer'] - const teams = await helpers.getTeamsByPermissions( - user.teams, - permissions, - context.models.Team, - ) - - let collectionsPermissions = await Promise.all( - teams.map(async team => { - let collection - if (team.object.type === 'collection') { - collection = await models.Collection.find(team.object.id) - } else if (team.object.type === 'fragment') { - const fragment = await models.Fragment.find(team.object.id) - collection = await models.Collection.find(fragment.collectionId) - } - if ( - collection.status === 'rejected' && - team.teamType.permissions === 'reviewer' - ) - return null - const collPerm = { - id: collection.id, - permission: team.teamType.permissions, - } - const objectType = get(object, 'type') - if (objectType === 'fragment') { - if (collection.fragments.includes(object.id)) - collPerm.fragmentId = object.id - else return null - } - - if (objectType === 'collection') - if (object.id !== collection.id) return null - return collPerm - }), - ) - collectionsPermissions = collectionsPermissions.filter(cp => cp !== null) - if (collectionsPermissions.length === 0) return false - - return { - filter: filterParam => { - if (!filterParam.length) { - return helpers.filterObjectData( - collectionsPermissions, - filterParam, - user, - ) - } - - const collections = filterParam - .map(coll => - helpers.filterObjectData(collectionsPermissions, coll, user), - ) - .filter(Boolean) - return collections - }, - } -} - -function unauthenticatedUser(operation, object) { - // Public/unauthenticated users can GET /collections, filtered by 'published' - if (operation === 'GET' && object && object.path === '/collections') { - return { - filter: collections => - collections.filter(collection => collection.published), - } - } - - // Public/unauthenticated users can GET /collections/:id/fragments, filtered by 'published' - if ( - operation === 'GET' && - object && - object.path === '/collections/:id/fragments' - ) { - return { - filter: fragments => fragments.filter(fragment => fragment.published), - } - } - - // and filtered individual collection's properties: id, title, source, content, owners - if (operation === 'GET' && object && object.type === 'collection') { - if (object.published) { - return { - filter: collection => - pickBy(collection, (_, key) => - ['id', 'title', 'owners'].includes(key), - ), - } - } - } - - if (operation === 'GET' && object && object.type === 'fragment') { - if (object.published) { - return { - filter: fragment => - pickBy(fragment, (_, key) => - ['id', 'title', 'source', 'presentation', 'owners'].includes(key), - ), - } - } - } - - return false -} - -async function authenticatedUser(user, operation, object, context) { - // Allow the authenticated user to POST a collection (but not with a 'filtered' property) - if (operation === 'POST' && object.path === '/collections') { - return { - filter: collection => omit(collection, 'filtered'), - } - } - - if ( - operation === 'POST' && - object.path === '/collections/:collectionId/fragments' - ) { - return true - } - - // allow authenticate owners full pass for a collection - if (get(object, 'type') === 'collection') { - if (operation === 'PATCH') { - return { - filter: collection => omit(collection, 'filtered'), - } - } - if (object.owners.includes(user.id)) return true - const owner = object.owners.find(own => own.id === user.id) - if (owner !== undefined) return true - } - - // Allow owners of a collection to GET its teams, e.g. - // GET /api/collections/1/teams - if (operation === 'GET' && get(object, 'path') === '/teams') { - const collectionId = get(object, 'params.collectionId') - if (collectionId) { - const collection = await context.models.Collection.find(collectionId) - if (collection.owners.includes(user.id)) { - return true - } - } - } - - if ( - operation === 'GET' && - get(object, 'type') === 'team' && - get(object, 'object.type') === 'collection' - ) { - const collection = await context.models.Collection.find( - get(object, 'object.id'), - ) - if (collection.owners.includes(user.id)) { - return true - } - } - - // Advanced example - // Allow authenticated users to create a team based around a collection - // if they are one of the owners of this collection - if (['POST', 'PATCH'].includes(operation) && get(object, 'type') === 'team') { - if (get(object, 'object.type') === 'collection') { - const collection = await context.models.Collection.find( - get(object, 'object.id'), - ) - if (collection.owners.includes(user.id)) { - return true - } - } - } - - // only allow the HE to create, delete an invitation, or get invitation details - if ( - ['POST', 'GET', 'DELETE'].includes(operation) && - get(object.collection, 'type') === 'collection' && - object.path.includes('invitations') - ) { - const collection = await context.models.Collection.find( - get(object.collection, 'id'), - ) - const handlingEditor = get(collection, 'handlingEditor') - if (!handlingEditor) return false - if (handlingEditor.id === user.id) return true - return false - } - - // only allow a reviewer and an HE to submit and to modify a recommendation - if ( - ['POST', 'PATCH'].includes(operation) && - object.path.includes('recommendations') - ) { - const authsomeObject = get(object, 'authsomeObject') - - const teams = await helpers.getTeamsByPermissions( - user.teams, - ['reviewer', 'handlingEditor'], - context.models.Team, - ) - - if (teams.length === 0) return false - const matchingTeam = teams.find( - team => team.object.id === authsomeObject.id, - ) - - if (matchingTeam) return true - return false - } - - if (user.teams.length !== 0 && ['GET'].includes(operation)) { - const permissions = await teamPermissions(user, operation, object, context) - - if (permissions) { - return permissions - } - - return false - } - - if (get(object, 'type') === 'fragment') { - const fragment = object - - if (fragment.owners.includes(user.id)) { - return true - } - } - - // A user can GET, DELETE and PATCH itself - if (get(object, 'type') === 'user' && get(object, 'id') === user.id) { - if (['GET', 'DELETE', 'PATCH'].includes(operation)) { - return true - } - } - // If no individual permissions exist (above), fallback to unauthenticated - // user's permission - return unauthenticatedUser(operation, object) -} - -const authsomeMode = async (userId, operation, object, context) => { - if (!userId) { - return unauthenticatedUser(operation, object) - } - - // It's up to us to retrieve the relevant models for our - // authorization/authsome mode, e.g. - const user = await context.models.User.find(userId) - - // Admins and editor in chiefs can do anything - if (user && (user.admin || user.editorInChief)) return true - - if (user) { - return authenticatedUser(user, operation, object, context) - } - - return false -} +const authsomeMode = require('xpub-faraday/config/authsome-mode') module.exports = authsomeMode diff --git a/packages/component-invite/config/default.js b/packages/component-invite/config/default.js index 4609e678e..9950c9b35 100644 --- a/packages/component-invite/config/default.js +++ b/packages/component-invite/config/default.js @@ -1,70 +1,3 @@ -const path = require('path') +const defaultConfig = require('xpub-faraday/config/default') -module.exports = { - authsome: { - mode: path.resolve(__dirname, 'authsome-mode.js'), - teams: { - handlingEditor: { - name: 'Handling Editors', - }, - reviewer: { - name: 'Reviewer', - }, - }, - }, - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer', 'author'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'HE Invited', - }, - heAssigned: { - public: 'HE Assigned', - private: 'HE Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +module.exports = defaultConfig diff --git a/packages/component-invite/config/test.js b/packages/component-invite/config/test.js index e622200e0..9950c9b35 100644 --- a/packages/component-invite/config/test.js +++ b/packages/component-invite/config/test.js @@ -1,71 +1,3 @@ -const path = require('path') +const defaultConfig = require('xpub-faraday/config/default') -module.exports = { - authsome: { - mode: path.resolve(__dirname, 'authsome-mode.js'), - teams: { - handlingEditor: { - name: 'Handling Editors', - }, - reviewer: { - name: 'Reviewer', - }, - }, - }, - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer', 'author'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author', 'handlingEditor', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - author: ['author'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'HE Invited', - }, - heAssigned: { - public: 'HE Assigned', - private: 'HE Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +module.exports = defaultConfig diff --git a/packages/component-invite/src/routes/fragmentsInvitations/get.js b/packages/component-invite/src/routes/fragmentsInvitations/get.js index 2bb8c540e..a7e80aa98 100644 --- a/packages/component-invite/src/routes/fragmentsInvitations/get.js +++ b/packages/component-invite/src/routes/fragmentsInvitations/get.js @@ -40,6 +40,7 @@ module.exports = models => async (req, res) => { fragment, path: req.route.path, } + const canGet = await authsome.can(req.user, 'GET', target) if (!canGet) diff --git a/packages/component-manuscript-manager/config/authsome-helpers.js b/packages/component-manuscript-manager/config/authsome-helpers.js deleted file mode 100644 index 658f47bb6..000000000 --- a/packages/component-manuscript-manager/config/authsome-helpers.js +++ /dev/null @@ -1,130 +0,0 @@ -const omit = require('lodash/omit') -const config = require('config') -const get = require('lodash/get') - -const statuses = config.get('statuses') - -const publicStatusesPermissions = ['author', 'reviewer'] - -const parseAuthorsData = (coll, matchingCollPerm) => { - if (['reviewer'].includes(matchingCollPerm.permission)) { - coll.authors = coll.authors.map(a => omit(a, ['email'])) - } -} - -const setPublicStatuses = (coll, matchingCollPerm) => { - const status = get(coll, 'status') || 'draft' - // coll.visibleStatus = statuses[status].public - if (publicStatusesPermissions.includes(matchingCollPerm.permission)) { - coll.visibleStatus = statuses[status].public - } -} - -const filterRefusedInvitations = (coll, user) => { - const matchingInv = coll.invitations.find(inv => inv.userId === user.id) - if (matchingInv === undefined) return null - if (matchingInv.hasAnswer === true && !matchingInv.isAccepted) return null - return coll -} - -const filterObjectData = ( - collectionsPermissions = [], - object = {}, - user = {}, -) => { - if (object.type === 'fragment') { - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.fragmentId, - ) - if (matchingCollPerm === undefined) return null - if (['reviewer'].includes(matchingCollPerm.permission)) { - object.files = omit(object.files, ['coverLetter']) - if (object.recommendations) - object.recommendations = object.recommendations.filter( - rec => rec.userId === user.id, - ) - } - parseAuthorsData(object, matchingCollPerm) - if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) { - return filterRefusedInvitations(object, user) - } - return object - } - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.id, - ) - if (matchingCollPerm === undefined) return null - setPublicStatuses(object, matchingCollPerm) - - return object -} - -const getTeamsByPermissions = async ( - teamIds = [], - permissions = [], - TeamModel, -) => - (await Promise.all( - teamIds.map(async teamId => { - const team = await TeamModel.find(teamId) - if (!permissions.includes(team.teamType.permissions)) { - return null - } - return team - }), - )).filter(Boolean) - -const heIsInvitedToFragment = async ({ user, Team, collectionId }) => - (await getTeamsByPermissions(user.teams, ['handlingEditor'], Team)).some( - // user is a member of the team with access to the fragment's parent collection - t => t.members.includes(user.id) && t.object.id === collectionId, - ) - -const getUserPermissions = async ({ - user, - Team, - mapFn = t => ({ - objectId: t.object.id, - objectType: t.object.type, - role: t.teamType.permissions, - }), -}) => - (await Promise.all(user.teams.map(teamId => Team.find(teamId)))).map(mapFn) - -const isOwner = ({ user: { id }, object }) => { - if (object.owners.includes(id)) return true - return !!object.owners.find(own => own.id === id) -} - -const hasPermissionForObject = async ({ user, object, Team, roles = [] }) => { - const userPermissions = await getUserPermissions({ - user, - Team, - }) - - return !!userPermissions.find(p => { - const hasObject = - p.objectId === get(object, 'fragment.id') || - p.objectId === get(object, 'fragment.collectionId') - if (roles.length > 0) { - return hasObject && roles.includes(p.role) - } - return hasObject - }) -} - -const isHandlingEditor = ({ user, object }) => - get(object, 'collection.handlingEditor.id') === user.id - -module.exports = { - filterObjectData, - parseAuthorsData, - setPublicStatuses, - getTeamsByPermissions, - filterRefusedInvitations, - isOwner, - isHandlingEditor, - getUserPermissions, - heIsInvitedToFragment, - hasPermissionForObject, -} diff --git a/packages/component-manuscript-manager/config/authsome-mode.js b/packages/component-manuscript-manager/config/authsome-mode.js index 2c80868dd..9c663beae 100644 --- a/packages/component-manuscript-manager/config/authsome-mode.js +++ b/packages/component-manuscript-manager/config/authsome-mode.js @@ -1,269 +1,3 @@ -const config = require('config') -const { get, pickBy, omit } = require('lodash') - -const statuses = config.get('statuses') -const helpers = require('./authsome-helpers') - -function unauthenticatedUser(operation, object) { - // Public/unauthenticated users can GET /collections, filtered by 'published' - if (operation === 'GET' && object && object.path === '/collections') { - return { - filter: collections => - collections.filter(collection => collection.published), - } - } - - // Public/unauthenticated users can GET /collections/:id/fragments, filtered by 'published' - if ( - operation === 'GET' && - object && - object.path === '/collections/:id/fragments' - ) { - return { - filter: fragments => fragments.filter(fragment => fragment.published), - } - } - - // and filtered individual collection's properties: id, title, source, content, owners - if (operation === 'GET' && object && object.type === 'collection') { - if (object.published) { - return { - filter: collection => - pickBy(collection, (_, key) => - ['id', 'title', 'owners'].includes(key), - ), - } - } - } - - if (operation === 'GET' && object && object.type === 'fragment') { - if (object.published) { - return { - filter: fragment => - pickBy(fragment, (_, key) => - ['id', 'title', 'source', 'presentation', 'owners'].includes(key), - ), - } - } - } - - return false -} - -const publicStatusesPermissions = ['author', 'reviewer'] -const createPaths = ['/collections', '/collections/:collectionId/fragments'] - -async function authenticatedUser(user, operation, object, context) { - if (operation === 'GET') { - if (get(object, 'path') === '/collections') { - return { - filter: async collections => { - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - return collections.filter(collection => { - if (collection.owners.includes(user.id)) { - return true - } - const collectionPermission = userPermissions.find( - p => p.objectId === collection.id, - ) - if (collectionPermission) { - return true - } - - const fragmentPermission = userPermissions.find(p => - collection.fragments.includes(p.objectId), - ) - if (fragmentPermission) { - return true - } - return false - }) - }, - } - } - - if (object === '/users') { - return true - } - - if (get(object, 'type') === 'collection') { - if (helpers.isOwner({ user, object })) { - return true - } - return { - filter: async collection => { - const status = get(collection, 'status') || 'draft' - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - if (collection.owners.map(o => o.id).includes(user.id)) { - return collection - } - - const collectionPermission = userPermissions.find( - p => p.objectId === collection.id, - ) - if ( - publicStatusesPermissions.includes( - get(collectionPermission, 'role'), - ) - ) { - collection.visibleStatus = statuses[status].public - } - return collection - }, - } - } - - if (get(object, 'type') === 'fragment') { - if (helpers.isOwner({ user, object })) { - return true - } - - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - - const permission = userPermissions.find( - p => p.objectId === object.id || p.objectId === object.collectionId, - ) - - if (!permission) return false - - return { - filter: fragment => { - // handle other roles - if (permission.role === 'reviewer') { - fragment.files = omit(fragment.files, ['coverLetter']) - fragment.authors = fragment.authors.map(a => omit(a, ['email'])) - } - return fragment - }, - } - } - - if (get(object, 'type') === 'user') { - return true - } - - // allow HE to get reviewer invitations - if (get(object, 'fragment.type') === 'fragment') { - const collectionId = get(object, 'fragment.collectionId') - const collection = await context.models.Collection.find(collectionId) - - if (get(collection, 'handlingEditor.id') === user.id) { - return true - } - } - - if (get(object, 'type') === 'user') { - return true - } - } - - if (operation === 'POST') { - // allow everytone to create manuscripts and versions - if (createPaths.includes(object.path)) { - return true - } - - // allow HE to invite - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/invitations' - ) { - return helpers.isHandlingEditor({ user, object }) - } - - // allow HE or assigned reviewers to recommend - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/recommendations' - ) { - return helpers.hasPermissionForObject({ - user, - object, - Team: context.models.Team, - roles: ['reviewer', 'handlingEditor'], - }) - } - } - - if (operation === 'PATCH') { - if (get(object, 'type') === 'collection') { - return helpers.isOwner({ user, object }) - } - - if (get(object, 'type') === 'fragment') { - return helpers.isOwner({ user, object }) - } - - // allow reviewer to patch his recommendation - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/recommendations/:recommendationId' - ) { - return helpers.hasPermissionForObject({ - user, - object, - Team: context.models.Team, - roles: ['reviewer'], - }) - } - - if (get(object, 'type') === 'user' && get(object, 'id') === user.id) { - return true - } - - // allow owner to submit a revision - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/submit' - ) { - return helpers.isOwner({ user, object: object.fragment }) - } - } - - if (operation === 'DELETE') { - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId' - ) { - return helpers.isHandlingEditor({ user, object }) - } - - if (get(object, 'type') === 'collection') { - return helpers.isOwner({ user, object }) - } - } - - // If no individual permissions exist (above), fallback to unauthenticated - // user's permission - return unauthenticatedUser(operation, object) -} - -const authsomeMode = async (userId, operation, object, context) => { - if (!userId) { - return unauthenticatedUser(operation, object) - } - - // It's up to us to retrieve the relevant models for our - // authorization/authsome mode, e.g. - const user = await context.models.User.find(userId) - - // Admins and editor in chiefs can do anything - if (user && (user.admin || user.editorInChief)) return true - - if (user) { - return authenticatedUser(user, operation, object, context) - } - - return false -} +const authsomeMode = require('xpub-faraday/config/authsome-mode') module.exports = authsomeMode diff --git a/packages/component-manuscript-manager/config/default.js b/packages/component-manuscript-manager/config/default.js index 241cb498d..9950c9b35 100644 --- a/packages/component-manuscript-manager/config/default.js +++ b/packages/component-manuscript-manager/config/default.js @@ -1,90 +1,3 @@ -const path = require('path') +const defaultConfig = require('xpub-faraday/config/default') -module.exports = { - authsome: { - mode: path.resolve(__dirname, 'authsome-mode.js'), - teams: { - handlingEditor: { - name: 'Handling Editors', - }, - reviewer: { - name: 'Reviewer', - }, - }, - }, - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer', 'author'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'Handling Editor Invited', - }, - heAssigned: { - public: 'Handling Editor Assigned', - private: 'Handling Editor Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - reviewCompleted: { - public: 'Under Review', - private: 'Review Completed', - }, - pendingApproval: { - public: 'Under Review', - private: 'Pending Approval', - }, - revisionRequested: { - public: 'Revision Requested', - private: 'Revision Requested', - }, - rejected: { - public: 'Rejected', - private: 'Rejected', - }, - published: { - public: 'Published', - private: 'Published', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +module.exports = defaultConfig diff --git a/packages/component-manuscript-manager/config/test.js b/packages/component-manuscript-manager/config/test.js index 9dad34bbf..9950c9b35 100644 --- a/packages/component-manuscript-manager/config/test.js +++ b/packages/component-manuscript-manager/config/test.js @@ -1,91 +1,3 @@ -const path = require('path') +const defaultConfig = require('xpub-faraday/config/default') -module.exports = { - authsome: { - mode: path.resolve(__dirname, 'authsome-mode.js'), - teams: { - handlingEditor: { - name: 'Handling Editors', - }, - reviewer: { - name: 'Reviewer', - }, - }, - }, - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer', 'author'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author', 'handlingEditor', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - author: ['author'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'Handling Editor Invited', - }, - heAssigned: { - public: 'Handling Editor Assigned', - private: 'Handling Editor Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - reviewCompleted: { - public: 'Under Review', - private: 'Review Completed', - }, - pendingApproval: { - public: 'Under Review', - private: 'Pending Approval', - }, - revisionRequested: { - public: 'Revision Requested', - private: 'Revision Requested', - }, - rejected: { - public: 'Rejected', - private: 'Rejected', - }, - published: { - public: 'Published', - private: 'Published', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +module.exports = defaultConfig diff --git a/packages/component-user-manager/config/authsome-helpers.js b/packages/component-user-manager/config/authsome-helpers.js deleted file mode 100644 index 55148df34..000000000 --- a/packages/component-user-manager/config/authsome-helpers.js +++ /dev/null @@ -1,131 +0,0 @@ -const omit = require('lodash/omit') -const config = require('config') -const get = require('lodash/get') - -const statuses = config.get('statuses') - -const publicStatusesPermissions = ['author', 'reviewer'] - -const parseAuthorsData = (coll, matchingCollPerm) => { - if (['reviewer'].includes(matchingCollPerm.permission)) { - coll.authors = coll.authors.map(a => omit(a, ['email'])) - } -} - -const setPublicStatuses = (coll, matchingCollPerm) => { - const status = get(coll, 'status') || 'draft' - // coll.visibleStatus = statuses[status].public - if (publicStatusesPermissions.includes(matchingCollPerm.permission)) { - coll.visibleStatus = statuses[status].public - } -} - -const filterRefusedInvitations = (coll, user) => { - const matchingInv = coll.invitations.find(inv => inv.userId === user.id) - if (matchingInv === undefined) return null - if (matchingInv.hasAnswer === true && !matchingInv.isAccepted) return null - return coll -} - -const filterObjectData = ( - collectionsPermissions = [], - object = {}, - user = {}, -) => { - if (object.type === 'fragment') { - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.fragmentId, - ) - if (matchingCollPerm === undefined) return null - if (['reviewer'].includes(matchingCollPerm.permission)) { - object.files = omit(object.files, ['coverLetter']) - if (object.recommendations) - object.recommendations = object.recommendations.filter( - rec => rec.userId === user.id, - ) - } - parseAuthorsData(object, matchingCollPerm) - if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) { - return filterRefusedInvitations(object, user) - } - return object - } - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.id, - ) - if (matchingCollPerm === undefined) return null - setPublicStatuses(object, matchingCollPerm) - - return object -} - -const getTeamsByPermissions = async ( - teamIds = [], - permissions = [], - TeamModel, -) => - (await Promise.all( - teamIds.map(async teamId => { - const team = await TeamModel.find(teamId) - if (!permissions.includes(team.teamType.permissions)) { - return null - } - return team - }), - )).filter(Boolean) - -const heIsInvitedToFragment = async ({ user, Team, collectionId }) => - (await getTeamsByPermissions(user.teams, ['handlingEditor'], Team)).some( - // user is a member of the team with access to the fragment's parent collection - t => t.members.includes(user.id) && t.object.id === collectionId, - ) - -const getUserPermissions = async ({ - user, - Team, - mapFn = t => ({ - objectId: t.object.id, - objectType: t.object.type, - role: t.teamType.permissions, - }), -}) => - (await Promise.all(user.teams.map(teamId => Team.find(teamId)))).map(mapFn) - -const isOwner = ({ user: { id }, object }) => { - if (object.owners.includes(id)) return true - return !!object.owners.find(own => own.id === id) -} - -const hasPermissionForObject = async ({ user, object, Team, roles = [] }) => { - const userPermissions = await getUserPermissions({ - user, - Team, - }) - - return !!userPermissions.find(p => { - const hasObject = - p.objectId === get(object, 'fragment.id') || - p.objectId === get(object, 'fragment.collectionId') - if (roles.length > 0) { - return hasObject && roles.includes(p.role) - } - return hasObject - }) -} - -const isHandlingEditor = ({ user, object }) => - get(object, 'collection.handlingEditor.id') === user.id - -module.exports = { - filterObjectData, - parseAuthorsData, - setPublicStatuses, - getTeamsByPermissions, - filterRefusedInvitations, - // - isOwner, - isHandlingEditor, - getUserPermissions, - heIsInvitedToFragment, - hasPermissionForObject, -} diff --git a/packages/component-user-manager/config/authsome-mode.js b/packages/component-user-manager/config/authsome-mode.js index 20e0d6918..9c663beae 100644 --- a/packages/component-user-manager/config/authsome-mode.js +++ b/packages/component-user-manager/config/authsome-mode.js @@ -1,253 +1,3 @@ -const config = require('config') -const { get, pickBy, omit } = require('lodash') - -const statuses = config.get('statuses') -const helpers = require('./authsome-helpers') - -function unauthenticatedUser(operation, object) { - // Public/unauthenticated users can GET /collections, filtered by 'published' - if (operation === 'GET' && object && object.path === '/collections') { - return { - filter: collections => - collections.filter(collection => collection.published), - } - } - - // Public/unauthenticated users can GET /collections/:id/fragments, filtered by 'published' - if ( - operation === 'GET' && - object && - object.path === '/collections/:id/fragments' - ) { - return { - filter: fragments => fragments.filter(fragment => fragment.published), - } - } - - // and filtered individual collection's properties: id, title, source, content, owners - if (operation === 'GET' && object && object.type === 'collection') { - if (object.published) { - return { - filter: collection => - pickBy(collection, (_, key) => - ['id', 'title', 'owners'].includes(key), - ), - } - } - } - - if (operation === 'GET' && object && object.type === 'fragment') { - if (object.published) { - return { - filter: fragment => - pickBy(fragment, (_, key) => - ['id', 'title', 'source', 'presentation', 'owners'].includes(key), - ), - } - } - } - - return false -} - -const publicStatusesPermissions = ['author', 'reviewer'] -const createPaths = ['/collections', '/collections/:collectionId/fragments'] - -async function authenticatedUser(user, operation, object, context) { - if (operation === 'GET') { - if (get(object, 'path') === '/collections') { - return { - filter: async collections => { - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - return collections.filter(collection => { - if (collection.owners.includes(user.id)) { - return true - } - const collectionPermission = userPermissions.find( - p => p.objectId === collection.id, - ) - if (collectionPermission) { - return true - } - - const fragmentPermission = userPermissions.find(p => - collection.fragments.includes(p.objectId), - ) - if (fragmentPermission) { - return true - } - return false - }) - }, - } - } - - if (object === '/users') { - return true - } - - if (get(object, 'type') === 'collection') { - if (helpers.isOwner({ user, object })) { - return true - } - return { - filter: async collection => { - const status = get(collection, 'status') || 'draft' - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - if (collection.owners.map(o => o.id).includes(user.id)) { - return collection - } - - const collectionPermission = userPermissions.find( - p => p.objectId === collection.id, - ) - if ( - publicStatusesPermissions.includes( - get(collectionPermission, 'role'), - ) - ) { - collection.visibleStatus = statuses[status].public - } - return collection - }, - } - } - - if (get(object, 'type') === 'fragment') { - if (helpers.isOwner({ user, object })) { - return true - } - - const userPermissions = await helpers.getUserPermissions({ - user, - Team: context.models.Team, - }) - - const permission = userPermissions.find( - p => p.objectId === object.id || p.objectId === object.collectionId, - ) - - if (!permission) return false - - return { - filter: fragment => { - // handle other roles - if (permission.role === 'reviewer') { - fragment.files = omit(fragment.files, ['coverLetter']) - fragment.authors = fragment.authors.map(a => omit(a, ['email'])) - } - return fragment - }, - } - } - - // allow HE to get reviewer invitations - if (get(object, 'fragment.type') === 'fragment') { - const collectionId = get(object, 'fragment.collectionId') - const collection = await context.models.Collection.find(collectionId) - - if (get(collection, 'handlingEditor.id') === user.id) { - return true - } - } - - if (get(object, 'type') === 'user') { - return true - } - } - - if (operation === 'POST') { - // allow everytone to create manuscripts and versions - if (createPaths.includes(object.path)) { - return true - } - - // allow HE to invite - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/invitations' - ) { - return helpers.isHandlingEditor({ user, object }) - } - - // allow HE or assigned reviewers to recommend - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/recommendations' - ) { - return helpers.hasPermissionForObject({ - user, - object, - Team: context.models.Team, - roles: ['reviewer', 'handlingEditor'], - }) - } - } - - if (operation === 'PATCH') { - if (get(object, 'type') === 'collection') { - return helpers.isOwner({ user, object }) - } - - if (get(object, 'type') === 'fragment') { - return helpers.isOwner({ user, object }) - } - - // allow reviewer to patch his recommendation - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/recommendations/:recommendationId' - ) { - return helpers.hasPermissionForObject({ - user, - object, - Team: context.models.Team, - roles: ['reviewer'], - }) - } - - if (get(object, 'type') === 'user' && get(object, 'id') === user.id) { - return true - } - } - - if (operation === 'DELETE') { - if ( - get(object, 'path') === - '/api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId' - ) { - return helpers.isHandlingEditor({ user, object }) - } - } - - // If no individual permissions exist (above), fallback to unauthenticated - // user's permission - return unauthenticatedUser(operation, object) -} - -const authsomeMode = async (userId, operation, object, context) => { - if (!userId) { - return unauthenticatedUser(operation, object) - } - - // It's up to us to retrieve the relevant models for our - // authorization/authsome mode, e.g. - const user = await context.models.User.find(userId) - - // Admins and editor in chiefs can do anything - if (user && (user.admin || user.editorInChief)) return true - - if (user) { - return authenticatedUser(user, operation, object, context) - } - - return false -} +const authsomeMode = require('xpub-faraday/config/authsome-mode') module.exports = authsomeMode diff --git a/packages/component-user-manager/config/default.js b/packages/component-user-manager/config/default.js index 5d8cb0c55..9950c9b35 100644 --- a/packages/component-user-manager/config/default.js +++ b/packages/component-user-manager/config/default.js @@ -1,90 +1,3 @@ -const path = require('path') +const defaultConfig = require('xpub-faraday/config/default') -module.exports = { - authsome: { - mode: path.resolve(__dirname, 'authsome-mode.js'), - teams: { - handlingEditor: { - name: 'Handling Editors', - }, - reviewer: { - name: 'Reviewer', - }, - }, - }, - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'Handling Editor Invited', - }, - heAssigned: { - public: 'Handling Editor Assigned', - private: 'Handling Editor Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - reviewCompleted: { - public: 'Under Review', - private: 'Review Completed', - }, - pendingApproval: { - public: 'Under Review', - private: 'Pending Approval', - }, - revisionRequested: { - public: 'Revision Requested', - private: 'Revision Requested', - }, - rejected: { - public: 'Rejected', - private: 'Rejected', - }, - published: { - public: 'Published', - private: 'Published', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +module.exports = defaultConfig diff --git a/packages/component-user-manager/config/test.js b/packages/component-user-manager/config/test.js index 63452331a..9950c9b35 100644 --- a/packages/component-user-manager/config/test.js +++ b/packages/component-user-manager/config/test.js @@ -1,78 +1,3 @@ -module.exports = { - mailer: { - from: 'test@example.com', - }, - 'invite-reset-password': { - url: - process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL || - 'http://localhost:3000/invite', - }, - roles: { - global: ['admin', 'editorInChief', 'author', 'handlingEditor'], - collection: ['handlingEditor', 'reviewer', 'author'], - inviteRights: { - admin: ['admin', 'editorInChief', 'author', 'handlingEditor', 'author'], - editorInChief: ['handlingEditor'], - handlingEditor: ['reviewer'], - author: ['author'], - }, - }, - statuses: { - draft: { - public: 'Draft', - private: 'Draft', - }, - submitted: { - public: 'Submitted', - private: 'Submitted', - }, - heInvited: { - public: 'Submitted', - private: 'Handling Editor Invited', - }, - heAssigned: { - public: 'Handling Editor Assigned', - private: 'Handling Editor Assigned', - }, - reviewersInvited: { - public: 'Reviewers Invited', - private: 'Reviewers Invited', - }, - underReview: { - public: 'Under Review', - private: 'Under Review', - }, - reviewCompleted: { - public: 'Under Review', - private: 'Review Completed', - }, - pendingApproval: { - public: 'Under Review', - private: 'Pending Approval', - }, - revisionRequested: { - public: 'Revision Requested', - private: 'Revision Requested', - }, - rejected: { - public: 'Rejected', - private: 'Rejected', - }, - published: { - public: 'Published', - private: 'Published', - }, - }, - 'manuscript-types': { - research: 'Research', - review: 'Review', - 'clinical-study': 'Clinical Study', - 'case-report': 'Case Report', - 'letter-to-editor': 'Letter to the Editor', - editorial: 'Editorial', - corrigendum: 'Corrigendum', - erratum: 'Erratum', - 'expression-of-concern': 'Expression of Concern', - retraction: 'Retraction', - }, -} +const defaultConfig = require('xpub-faraday/config/default') + +module.exports = defaultConfig -- GitLab