From 068bd67ab7947667f74e4ac8a2c47c89c0502b3e Mon Sep 17 00:00:00 2001
From: Jure Triglav <juretriglav@gmail.com>
Date: Tue, 29 Sep 2020 01:03:21 +0200
Subject: [PATCH] feat: improve permissions to support multiple manuscript
 versions

---
 config/default.js     |  41 +++++------
 config/permissions.js | 159 +++++++++++++++++++++++++++---------------
 2 files changed, 121 insertions(+), 79 deletions(-)

diff --git a/config/default.js b/config/default.js
index 3986e83430..62ecdfb4fd 100644
--- a/config/default.js
+++ b/config/default.js
@@ -3,30 +3,25 @@ const components = require('./components.json')
 const logger = require('winston')
 
 module.exports = {
+  teams: {
+    seniorEditor: {
+      name: 'Senior Editor',
+    },
+    handlingEditor: {
+      name: 'Handling Editor',
+    },
+    managingEditor: {
+      name: 'Managing Editor',
+    },
+    reviewer: {
+      name: 'Reviewer',
+    },
+    author: {
+      name: 'Author',
+    },
+  },
   authsome: {
     mode: path.resolve(__dirname, 'authsome.js'),
-    teams: {
-      seniorEditor: {
-        name: 'Senior Editors',
-        permissions: '',
-      },
-      handlingEditor: {
-        name: 'Handling Editors',
-        permissions: '',
-      },
-      managingEditor: {
-        name: 'Managing Editors',
-        permissions: '',
-      },
-      reviewer: {
-        name: 'Reviewer',
-        permissions: '',
-      },
-      author: {
-        name: 'Authors',
-        permissions: '',
-      },
-    },
   },
   validations: path.resolve(__dirname, 'validations.js'),
   pubsweet: {
@@ -149,13 +144,13 @@ module.exports = {
   },
   publicKeys: [
     'pubsweet-client',
-    'authsome',
     'validations',
     'pubsweet-component-xpub-dashboard',
     'pubsweet-component-xpub-formbuilder',
     'pubsweet',
     'detectionMethodCorrelations',
     'journal',
+    'teams',
   ],
   schema: {},
   journal: require('./journal'),
diff --git a/config/permissions.js b/config/permissions.js
index 583bf92a18..236ab7d55f 100644
--- a/config/permissions.js
+++ b/config/permissions.js
@@ -106,16 +106,22 @@ const userIsAllowedToChat = rule({ cache: 'strict' })(
 const user_is_review_author_and_review_is_not_completed = rule({
   cache: 'strict',
 })(async (parent, args, ctx, info) => {
-  const review = await ctx.models.Review.query().findById(args.id)
-  const manuscript = await ctx.models.Manuscript.query().findById(
-    review.manuscriptId,
-  )
+  let manuscriptId
+  if (args.id) {
+    ;({ manuscriptId } = await ctx.models.Review.query().findById(args.id))
+  } else {
+    ;({ manuscriptId } = args.input)
+  }
+
+  const manuscript = await ctx.models.Manuscript.query().findById(manuscriptId)
   const team = await ctx.models.Team.query()
     .where({
       manuscriptId: manuscript.id,
       role: 'reviewer',
     })
     .first()
+  if (!team) return false
+
   const members = await team
     .$relatedQuery('members')
     .where('userId', ctx.user.id)
@@ -127,6 +133,19 @@ const user_is_review_author_and_review_is_not_completed = rule({
   return false
 })
 
+const user_is_editor_of_the_manuscript_of_the_review = rule({
+  cache: 'strict',
+})(async (parent, args, ctx, info) => {
+  let manuscriptId
+  if (args.id) {
+    ;({ manuscriptId } = await ctx.models.Review.query().findById(args.id))
+  } else {
+    ;({ manuscriptId } = args.input)
+  }
+
+  return _userIsEditor(ctx.user, manuscriptId)
+})
+
 const user_is_invited_reviewer = rule({ cache: 'strict' })(
   async (parent, args, ctx, info) => {
     const team = await ctx.models.Team.query().findById(args.teamId)
@@ -187,62 +206,90 @@ const current_user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_
   return false
 })
 
-const permissions = shield(
-  {
-    Query: {
-      currentUser: isAuthenticated,
-      paginatedManuscripts: userIsAdmin,
-      detailsForURL: allow,
-      publishedManuscripts: allow,
-      manuscripts: allow,
-      manuscript: allow,
-      messages: allow,
-      getFile: allow, // this is a query that gets the form
-    },
-    Mutation: {
-      createManuscript: isAuthenticated,
-      updateManuscript: user_is_author,
-      createMessage: userIsAllowedToChat,
-      updateReview: user_is_review_author_and_review_is_not_completed,
-      reviewerResponse: user_is_invited_reviewer,
-      completeReview: user_is_review_author_and_review_is_not_completed,
-    },
-    Subscription: {
-      messageCreated: userIsAllowedToChat,
-    },
-    CurrentRole: allow,
-    Team: allow,
-    TeamMember: allow,
-    URLMetadata: allow,
-    User: allow,
-    PaginatedManuscripts: allow,
-    Manuscript: allow,
-    File: or(
-      parent_manuscript_is_published,
-      or(
-        current_user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_complete,
-        userIsEditor,
-        userIsAdmin,
-      ),
+const permissions = {
+  Query: {
+    currentUser: isAuthenticated,
+    paginatedManuscripts: userIsAdmin,
+    detailsForURL: allow,
+    publishedManuscripts: allow,
+    manuscripts: allow,
+    manuscript: allow,
+    messages: allow,
+    getFile: allow, // this is a query that gets the form
+    user: allow,
+  },
+  Mutation: {
+    createManuscript: isAuthenticated,
+    updateManuscript: user_is_author,
+    submitManuscript: user_is_author,
+    createMessage: userIsAllowedToChat,
+    updateReview: or(
+      user_is_review_author_and_review_is_not_completed,
+      user_is_editor_of_the_manuscript_of_the_review,
+    ),
+    reviewerResponse: user_is_invited_reviewer,
+    completeReview: or(
+      user_is_review_author_and_review_is_not_completed,
+      user_is_editor_of_the_manuscript_of_the_review,
     ),
-    Review: or(parent_manuscript_is_published, review_is_by_current_user),
-    ReviewComment: allow,
-    Channel: allow,
-    Message: allow,
-    MessagesRelay: allow,
-    PageInfo: allow,
-    ManuscriptMeta: allow,
-    Note: allow,
-    Identity: allow,
+    createNewVersion: allow,
   },
-  {
-    allowExternalErrors: false,
-    debug: true,
-    fallbackRule: or(userIsAdmin, userIsEditor),
+  Subscription: {
+    messageCreated: userIsAllowedToChat,
   },
-)
+  CurrentRole: allow,
+  Team: allow,
+  TeamMember: allow,
+  URLMetadata: allow,
+  User: allow,
+  PaginatedManuscripts: allow,
+  Manuscript: allow,
+  ManuscriptVersion: allow,
+  File: or(
+    parent_manuscript_is_published,
+    or(
+      current_user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_complete,
+      userIsEditor,
+      userIsAdmin,
+    ),
+  ),
+  Review: or(parent_manuscript_is_published, review_is_by_current_user),
+  ReviewComment: allow,
+  Channel: allow,
+  Message: allow,
+  MessagesRelay: allow,
+  PageInfo: allow,
+  ManuscriptMeta: allow,
+  Note: allow,
+  Identity: allow,
+}
+
+const fallbackRule = or(userIsAdmin, userIsEditor)
+
+// We only ever need to go two levels down, so no need for recursion
+const addOverrideRule = permissions => {
+  const adaptedPermissions = {}
+  Object.keys(permissions).forEach(key1 => {
+    const value = permissions[key1]
+    if (value.constructor.name !== 'Object') {
+      adaptedPermissions[key1] = or(fallbackRule, value)
+    } else {
+      adaptedPermissions[key1] = value
+      Object.keys(value).forEach(key2 => {
+        adaptedPermissions[key1][key2] = or(fallbackRule, value[key2])
+      })
+    }
+  })
+  return adaptedPermissions
+}
+
+const shieldWithPermissions = shield(addOverrideRule(permissions), {
+  allowExternalErrors: false,
+  debug: true,
+  fallbackRule,
+})
 
-module.exports = permissions
+module.exports = shieldWithPermissions
 
 // const userIsEditorOfManuscript = rule({
 //   cache: 'strict',
-- 
GitLab