fix: prevent write:groups from self-promoting
This commit is contained in:
		| @@ -286,6 +286,34 @@ module.exports = { | |||||||
|     return false |     return false | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Check for exclusive permissions (contain any X permission(s) but not any Y permission(s)) | ||||||
|  |    * | ||||||
|  |    * @param {User} user | ||||||
|  |    * @param {Array<String>} includePermissions | ||||||
|  |    * @param {Array<String>} excludePermissions | ||||||
|  |    */ | ||||||
|  |   checkExclusiveAccess(user, includePermissions = [], excludePermissions = []) { | ||||||
|  |     const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions() | ||||||
|  |  | ||||||
|  |     // System Admin | ||||||
|  |     if (userPermissions.includes('manage:system')) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Check Inclusion Permissions | ||||||
|  |     if (_.intersection(userPermissions, includePermissions).length < 1) { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Check Exclusion Permissions | ||||||
|  |     if (_.intersection(userPermissions, excludePermissions).length > 0) { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true | ||||||
|  |   }, | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Check and apply Page Rule specificity |    * Check and apply Page Rule specificity | ||||||
|    * |    * | ||||||
|   | |||||||
| @@ -1,39 +1,68 @@ | |||||||
| const graphHelper = require('../../helpers/graph') | const graphHelper = require('../../helpers/graph') | ||||||
| const safeRegex = require('safe-regex') | const safeRegex = require('safe-regex') | ||||||
| const _ = require('lodash') | const _ = require('lodash') | ||||||
|  | const gql = require('graphql') | ||||||
|  |  | ||||||
| /* global WIKI */ | /* global WIKI */ | ||||||
|  |  | ||||||
| const gql = require('graphql') |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   Query: { |   Query: { | ||||||
|     async groups() { return {} } |     async groups () { return {} } | ||||||
|   }, |   }, | ||||||
|   Mutation: { |   Mutation: { | ||||||
|     async groups() { return {} } |     async groups () { return {} } | ||||||
|   }, |   }, | ||||||
|   GroupQuery: { |   GroupQuery: { | ||||||
|     async list(obj, args, context, info) { |     /** | ||||||
|  |      * FETCH ALL GROUPS | ||||||
|  |      */ | ||||||
|  |     async list () { | ||||||
|       return WIKI.models.groups.query().select( |       return WIKI.models.groups.query().select( | ||||||
|         'groups.*', |         'groups.*', | ||||||
|         WIKI.models.groups.relatedQuery('users').count().as('userCount') |         WIKI.models.groups.relatedQuery('users').count().as('userCount') | ||||||
|       ) |       ) | ||||||
|     }, |     }, | ||||||
|     async single(obj, args, context, info) { |     /** | ||||||
|  |      * FETCH A SINGLE GROUP | ||||||
|  |      */ | ||||||
|  |     async single(obj, args) { | ||||||
|       return WIKI.models.groups.query().findById(args.id) |       return WIKI.models.groups.query().findById(args.id) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   GroupMutation: { |   GroupMutation: { | ||||||
|     async assignUser(obj, args) { |     /** | ||||||
|  |      * ASSIGN USER TO GROUP | ||||||
|  |      */ | ||||||
|  |     async assignUser (obj, args, { req }) { | ||||||
|  |       // Check for guest user | ||||||
|  |       if (args.userId === 2) { | ||||||
|  |         throw new gql.GraphQLError('Cannot assign the Guest user to a group.') | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Check for valid group | ||||||
|       const grp = await WIKI.models.groups.query().findById(args.groupId) |       const grp = await WIKI.models.groups.query().findById(args.groupId) | ||||||
|       if (!grp) { |       if (!grp) { | ||||||
|         throw new gql.GraphQLError('Invalid Group ID') |         throw new gql.GraphQLError('Invalid Group ID') | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Check assigned permissions for write:groups | ||||||
|  |       if ( | ||||||
|  |         WIKI.auth.checkExclusiveAccess(req.user, ['write:groups'], ['manage:groups', 'manage:system']) && | ||||||
|  |         grp.permissions.some(p => { | ||||||
|  |           const resType = _.last(p.split(':')) | ||||||
|  |           return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType) | ||||||
|  |         }) | ||||||
|  |       ) { | ||||||
|  |         throw new gql.GraphQLError('You are not authorized to assign a user to this elevated group.') | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Check for valid user | ||||||
|       const usr = await WIKI.models.users.query().findById(args.userId) |       const usr = await WIKI.models.users.query().findById(args.userId) | ||||||
|       if (!usr) { |       if (!usr) { | ||||||
|         throw new gql.GraphQLError('Invalid User ID') |         throw new gql.GraphQLError('Invalid User ID') | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Check for existing relation | ||||||
|       const relExist = await WIKI.models.knex('userGroups').where({ |       const relExist = await WIKI.models.knex('userGroups').where({ | ||||||
|         userId: args.userId, |         userId: args.userId, | ||||||
|         groupId: args.groupId |         groupId: args.groupId | ||||||
| @@ -41,8 +70,11 @@ module.exports = { | |||||||
|       if (relExist) { |       if (relExist) { | ||||||
|         throw new gql.GraphQLError('User is already assigned to group.') |         throw new gql.GraphQLError('User is already assigned to group.') | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Assign user to group | ||||||
|       await grp.$relatedQuery('users').relate(usr.id) |       await grp.$relatedQuery('users').relate(usr.id) | ||||||
|  |  | ||||||
|  |       // Revoke tokens for this user | ||||||
|       WIKI.auth.revokeUserTokens({ id: usr.id, kind: 'u' }) |       WIKI.auth.revokeUserTokens({ id: usr.id, kind: 'u' }) | ||||||
|       WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' }) |       WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' }) | ||||||
|  |  | ||||||
| @@ -50,7 +82,10 @@ module.exports = { | |||||||
|         responseResult: graphHelper.generateSuccess('User has been assigned to group.') |         responseResult: graphHelper.generateSuccess('User has been assigned to group.') | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async create(obj, args) { |     /** | ||||||
|  |      * CREATE NEW GROUP | ||||||
|  |      */ | ||||||
|  |     async create (obj, args, { req }) { | ||||||
|       const group = await WIKI.models.groups.query().insertAndFetch({ |       const group = await WIKI.models.groups.query().insertAndFetch({ | ||||||
|         name: args.name, |         name: args.name, | ||||||
|         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), |         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), | ||||||
| @@ -64,7 +99,14 @@ module.exports = { | |||||||
|         group |         group | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async delete(obj, args) { |     /** | ||||||
|  |      * DELETE GROUP | ||||||
|  |      */ | ||||||
|  |     async delete (obj, args) { | ||||||
|  |       if (args.id === 1 || args.id === 2) { | ||||||
|  |         throw new gql.GraphQLError('Cannot delete this group.') | ||||||
|  |       } | ||||||
|  |  | ||||||
|       await WIKI.models.groups.query().deleteById(args.id) |       await WIKI.models.groups.query().deleteById(args.id) | ||||||
|  |  | ||||||
|       WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) |       WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) | ||||||
| @@ -77,7 +119,16 @@ module.exports = { | |||||||
|         responseResult: graphHelper.generateSuccess('Group has been deleted.') |         responseResult: graphHelper.generateSuccess('Group has been deleted.') | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async unassignUser(obj, args) { |     /** | ||||||
|  |      * UNASSIGN USER FROM GROUP | ||||||
|  |      */ | ||||||
|  |     async unassignUser (obj, args) { | ||||||
|  |       if (args.userId === 2) { | ||||||
|  |         throw new gql.GraphQLError('Cannot unassign Guest user') | ||||||
|  |       } | ||||||
|  |       if (args.userId === 1 && args.groupId === 1) { | ||||||
|  |         throw new gql.GraphQLError('Cannot unassign Administrator user from Administrators group.') | ||||||
|  |       } | ||||||
|       const grp = await WIKI.models.groups.query().findById(args.groupId) |       const grp = await WIKI.models.groups.query().findById(args.groupId) | ||||||
|       if (!grp) { |       if (!grp) { | ||||||
|         throw new gql.GraphQLError('Invalid Group ID') |         throw new gql.GraphQLError('Invalid Group ID') | ||||||
| @@ -95,17 +146,34 @@ module.exports = { | |||||||
|         responseResult: graphHelper.generateSuccess('User has been unassigned from group.') |         responseResult: graphHelper.generateSuccess('User has been unassigned from group.') | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async update(obj, args) { |     /** | ||||||
|  |      * UPDATE GROUP | ||||||
|  |      */ | ||||||
|  |     async update (obj, args, { req }) { | ||||||
|  |       // Check for unsafe regex page rules | ||||||
|       if (_.some(args.pageRules, pr => { |       if (_.some(args.pageRules, pr => { | ||||||
|         return pr.match === 'REGEX' && !safeRegex(pr.path) |         return pr.match === 'REGEX' && !safeRegex(pr.path) | ||||||
|       })) { |       })) { | ||||||
|         throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.') |         throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.') | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Set default redirect on login value | ||||||
|       if (_.isEmpty(args.redirectOnLogin)) { |       if (_.isEmpty(args.redirectOnLogin)) { | ||||||
|         args.redirectOnLogin = '/' |         args.redirectOnLogin = '/' | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Check assigned permissions for write:groups | ||||||
|  |       if ( | ||||||
|  |         WIKI.auth.checkExclusiveAccess(req.user, ['write:groups'], ['manage:groups', 'manage:system']) && | ||||||
|  |         args.permissions.some(p => { | ||||||
|  |           const resType = _.last(p.split(':')) | ||||||
|  |           return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType) | ||||||
|  |         }) | ||||||
|  |       ) { | ||||||
|  |         throw new gql.GraphQLError('You are not authorized to manage this group or assign these permissions.') | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Update group | ||||||
|       await WIKI.models.groups.query().patch({ |       await WIKI.models.groups.query().patch({ | ||||||
|         name: args.name, |         name: args.name, | ||||||
|         redirectOnLogin: args.redirectOnLogin, |         redirectOnLogin: args.redirectOnLogin, | ||||||
| @@ -113,9 +181,11 @@ module.exports = { | |||||||
|         pageRules: JSON.stringify(args.pageRules) |         pageRules: JSON.stringify(args.pageRules) | ||||||
|       }).where('id', args.id) |       }).where('id', args.id) | ||||||
|  |  | ||||||
|  |       // Revoke tokens for this group | ||||||
|       WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) |       WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) | ||||||
|       WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'g' }) |       WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'g' }) | ||||||
|  |  | ||||||
|  |       // Reload group permissions | ||||||
|       await WIKI.auth.reloadGroups() |       await WIKI.auth.reloadGroups() | ||||||
|       WIKI.events.outbound.emit('reloadGroups') |       WIKI.events.outbound.emit('reloadGroups') | ||||||
|  |  | ||||||
| @@ -125,7 +195,7 @@ module.exports = { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   Group: { |   Group: { | ||||||
|     users(grp) { |     users (grp) { | ||||||
|       return grp.$relatedQuery('users') |       return grp.$relatedQuery('users') | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user