Add permissions on upload controller

Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>
This commit is contained in:
Convly 2020-07-02 18:49:20 +02:00 committed by Alexandre Bodin
parent c31c86c8e5
commit 5f01d9ebe1
7 changed files with 422 additions and 270 deletions

View File

@ -6,77 +6,20 @@
*/
const _ = require('lodash');
const validateSettings = require('./validation/settings');
const { yup, formatYupErrors } = require('strapi-utils');
const apiUploadController = require('./api-upload');
const apiAdminUploadController = require('./api-admin-upload');
const apiUploadController = require('./upload/api');
const adminUploadController = require('./upload/admin');
const validateUploadBody = require('./validation/upload');
const fileInfoSchema = yup.object({
name: yup.string().nullable(),
alternativeText: yup.string().nullable(),
caption: yup.string().nullable(),
});
const uploadSchema = yup.object({
fileInfo: fileInfoSchema,
});
const multiUploadSchema = yup.object({
fileInfo: yup.array().of(fileInfoSchema),
});
const validateUploadBody = (schema, data = {}) => {
return schema.validate(data, { abortEarly: false }).catch(err => {
throw strapi.errors.badRequest('ValidationError', { errors: formatYupErrors(err) });
});
};
const isUploadDisabled = () => _.get(strapi.plugins, 'upload.config.enabled', true) === false;
const disabledPluginError = () =>
strapi.errors.badRequest(null, {
errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }],
});
const emptyFileError = () =>
strapi.errors.badRequest(null, {
errors: [{ id: 'Upload.status.empty', message: 'Files are empty' }],
});
// Assign created_by and updated_by on medias
const setCreatorInfo = (ctx, files, { edition = false } = {}) => {
if (!Array.isArray(files)) files = [files];
return Promise.all(
files.map(file => {
if (ctx.state.isAuthenticatedAdmin) {
if (edition) {
return strapi.query('file', 'upload').update(
{ id: file.id },
{
updated_by: ctx.state.user.id,
}
);
}
return strapi.query('file', 'upload').update(
{ id: file.id },
{
created_by: ctx.state.user.id,
updated_by: ctx.state.user.id,
}
);
}
})
);
};
const resolveControllerFor = method => ctx => {
const resolveController = ctx => {
const {
state: { isAuthenticatedAdmin },
} = ctx;
const controller = isAuthenticatedAdmin ? apiAdminUploadController : apiUploadController;
return isAuthenticatedAdmin ? adminUploadController : apiUploadController;
};
const resolveControllerMethod = method => ctx => {
const controller = resolveController(ctx);
const callbackFn = controller[method];
if (!_.isFunction(callbackFn)) {
@ -87,72 +30,43 @@ const resolveControllerFor = method => ctx => {
};
module.exports = {
find: resolveControllerMethod('find'),
findOne: resolveControllerMethod('findOne'),
count: resolveControllerMethod('count'),
destroy: resolveControllerMethod('destroy'),
updateSettings: resolveControllerMethod('updateSettings'),
getSettings: resolveControllerMethod('getSettings'),
async upload(ctx) {
if (isUploadDisabled()) {
throw disabledPluginError();
const isUploadDisabled = _.get(strapi.plugins, 'upload.config.enabled', true) === false;
if (isUploadDisabled) {
throw strapi.errors.badRequest(null, {
errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }],
});
}
const { id } = ctx.query;
const files = _.get(ctx.request.files, 'files');
const {
query: { id },
request: { body, files: { files } = {} },
} = ctx.query;
const controller = resolveController(ctx);
// update only fileInfo if not file content sent
if (id && (_.isEmpty(files) || files.size === 0)) {
const data = await validateUploadBody(uploadSchema, ctx.request.body);
const file = await strapi.plugins.upload.services.upload.updateFileInfo(id, data.fileInfo);
await setCreatorInfo(ctx, file, { edition: true });
return (ctx.body = file);
return controller.updateFileInfo(ctx);
}
if (_.isEmpty(files) || files.size === 0) {
throw emptyFileError();
throw strapi.errors.badRequest(null, {
errors: [{ id: 'Upload.status.empty', message: 'Files are empty' }],
});
}
const uploadService = strapi.plugins.upload.services.upload;
const data = await validateUploadBody(body);
const validationSchema = Array.isArray(files) ? multiUploadSchema : uploadSchema;
const data = await validateUploadBody(validationSchema, ctx.request.body);
if (id) {
// cannot replace with more than one file
if (Array.isArray(files)) {
throw strapi.errors.badRequest(null, {
errors: [
{ id: 'Upload.replace.single', message: 'Cannot replace a file with multiple ones' },
],
});
}
const file = await uploadService.replace(id, { data, file: files });
await setCreatorInfo(ctx, file, { edition: true });
ctx.body = file;
} else {
const file = await uploadService.upload({ data, files });
await setCreatorInfo(ctx, file);
ctx.body = file;
}
await (id ? controller.replaceFile : controller.uploadFiles)(ctx, data, files);
},
async getSettings(ctx) {
const data = await strapi.plugins.upload.services.upload.getSettings();
ctx.body = { data };
},
async updateSettings(ctx) {
const data = await validateSettings(ctx.request.body);
await strapi.plugins.upload.services.upload.setSettings(data);
ctx.body = { data };
},
find: resolveControllerFor('find'),
findOne: resolveControllerFor('findOne'),
count: resolveControllerFor('count'),
destroy: resolveControllerFor('destroy'),
async search(ctx) {
const { id } = ctx.params;

View File

@ -1,104 +0,0 @@
'use strict';
const _ = require('lodash');
const ACTIONS = {
read: 'plugins::upload.read',
readSettings: 'plugins::upload.settings.read',
create: 'plugins::upload.assets.create',
update: 'plugins::upload.assets.update',
download: 'plugins::upload.assets.download',
copyLink: 'plugins::upload.assets.copy-link',
};
const fileModel = 'plugins::upload.file';
module.exports = {
async find(ctx) {
const {
state: { userAbility },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.read,
fileModel
);
const method = _.has(ctx.query, '_q') ? 'search' : 'fetchAll';
const query = pm.queryFrom(ctx.query);
const result = await strapi.plugins.upload.services.upload[method](query);
ctx.body = pm.sanitize(result);
},
async findOne(ctx) {
const {
state: { userAbility },
params: { id },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.read,
fileModel
);
if (!pm.isAllowed) {
return ctx.forbidden();
}
const data = await strapi.plugins['upload'].services.upload.fetch({
_where: [pm.query, { id }],
});
if (!data) {
return ctx.notFound('file.notFound');
}
ctx.body = pm.sanitize(data);
},
async count(ctx) {
const pm = strapi.admin.services.permission.createPermissionsManager(
ctx.state.userAbility,
ACTIONS.read,
fileModel
);
const method = _.has(ctx.query, '_q') ? 'countSearch' : 'count';
const query = pm.queryFrom(ctx.query);
const count = await strapi.plugins.upload.services.upload[method](query);
ctx.body = { count };
},
async destroy(ctx) {
const {
state: { userAbility },
params: { id },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.update,
fileModel
);
const file = await strapi.plugins['upload'].services.upload.fetch({ id });
if (!file) {
return ctx.notFound('file.notFound');
}
if (pm.ability.cannot(ACTIONS.update, pm.toSubject(file))) {
return ctx.forbidden();
}
await strapi.plugins['upload'].services.upload.remove(file);
ctx.body = pm.sanitize(file, { action: ACTIONS.read });
},
};

View File

@ -1,47 +0,0 @@
'use strict';
const _ = require('lodash');
module.exports = {
async find(ctx) {
const method = _.has(ctx.query, '_q') ? 'search' : 'fetchAll';
ctx.body = await strapi.plugins.upload.services.upload[method](ctx.query);
},
async findOne(ctx) {
const {
params: { id },
} = ctx;
const data = await strapi.plugins.upload.services.upload.fetch({ id });
if (!data) {
return ctx.notFound('file.notFound');
}
ctx.body = data;
},
async count(ctx) {
const method = _.has(ctx.query, '_q') ? 'countSearch' : 'count';
ctx.body = await strapi.plugins.upload.services.upload[method](ctx.query);
},
async destroy(ctx) {
const {
params: { id },
} = ctx;
const file = await strapi.plugins['upload'].services.upload.fetch({ id });
if (!file) {
return ctx.notFound('file.notFound');
}
await strapi.plugins['upload'].services.upload.remove(file);
ctx.body = file;
},
};

View File

@ -0,0 +1,236 @@
'use strict';
const _ = require('lodash');
const validateSettings = require('../validation/settings');
const validateUploadBody = require('../validation/upload');
const ACTIONS = {
read: 'plugins::upload.read',
readSettings: 'plugins::upload.settings.read',
create: 'plugins::upload.assets.create',
update: 'plugins::upload.assets.update',
download: 'plugins::upload.assets.download',
copyLink: 'plugins::upload.assets.copy-link',
};
const fileModel = 'plugins::upload.file';
module.exports = {
async find(ctx) {
const {
state: { userAbility },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.read,
fileModel
);
if (!pm.isAllowed) {
return ctx.forbidden();
}
const method = _.has(ctx.query, '_q') ? 'search' : 'fetchAll';
const query = pm.queryFrom(ctx.query);
const result = await strapi.plugins.upload.services.upload[method](query);
ctx.body = pm.sanitize(result);
},
async findOne(ctx) {
const {
state: { userAbility },
params: { id },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.read,
fileModel
);
if (!pm.isAllowed) {
return ctx.forbidden();
}
const data = await strapi.plugins.upload.services.upload.fetch({ id });
if (!data) {
return ctx.notFound('file.notFound');
}
if (pm.ability.cannot(pm.action, pm.toSubject(data))) {
return ctx.forbidden();
}
ctx.body = pm.sanitize(data);
},
async count(ctx) {
const pm = strapi.admin.services.permission.createPermissionsManager(
ctx.state.userAbility,
ACTIONS.read,
fileModel
);
if (!pm.isAllowed) {
return ctx.forbidden();
}
const method = _.has(ctx.query, '_q') ? 'countSearch' : 'count';
const query = pm.queryFrom(ctx.query);
const count = await strapi.plugins.upload.services.upload[method](query);
ctx.body = { count };
},
async destroy(ctx) {
const {
state: { userAbility },
params: { id },
} = ctx;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.update,
fileModel
);
if (!pm.isAllowed) {
return ctx.forbidden();
}
const file = await strapi.plugins['upload'].services.upload.fetch({ id });
if (!file) {
return ctx.notFound('file.notFound');
}
if (pm.ability.cannot(ACTIONS.update, pm.toSubject(file))) {
return ctx.forbidden();
}
await strapi.plugins['upload'].services.upload.remove(file);
ctx.body = pm.sanitize(file, { action: ACTIONS.read });
},
async updateSettings(ctx) {
const {
request: { body },
state: { userAbility },
} = ctx;
if (userAbility.canot(ACTIONS.read, fileModel)) {
return ctx.forbidden();
}
const data = await validateSettings(body);
await strapi.plugins.upload.services.upload.setSettings(data);
ctx.body = { data };
},
async getSettings(ctx) {
const {
state: { userAbility },
} = ctx;
if (userAbility.cannot(ACTIONS.read, fileModel)) {
return ctx.forbidden();
}
const data = await strapi.plugins.upload.services.upload.getSettings();
ctx.body = { data };
},
async updateFileInfo(ctx) {
const {
state: { userAbility, user },
query: { id },
request: { body },
} = ctx;
const uploadService = strapi.plugins.upload.services.upload;
const [pm] = findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
const data = await validateUploadBody(body);
const file = await uploadService.updateFileInfo(id, data.fileInfo);
await uploadService.setCreatorInfo(user.id, file, { edition: true });
ctx.body = pm.sanitize(file, { action: ACTIONS.read });
},
async replaceFile(ctx) {
const {
state: { userAbility, user },
query: { id },
request: { body, files: { files } = {} },
} = ctx;
const uploadService = strapi.plugins.upload.services.upload;
const [pm] = findEntityAndCheckPermissions(userAbility, ACTIONS.update, fileModel, id);
if (Array.isArray(files)) {
throw strapi.errors.badRequest(null, {
errors: [
{ id: 'Upload.replace.single', message: 'Cannot replace a file with multiple ones' },
],
});
}
const data = await validateUploadBody(body);
const file = await uploadService.replace(id, { data, file: files });
await uploadService.setCreatorInfo(user.id, file, { edition: true });
ctx.body = pm.sanitize(file, { action: ACTIONS.read });
},
async uploadFiles(ctx) {
const {
state: { userAbility, user },
request: { body, files: { files } = {} },
} = ctx.query;
const uploadService = strapi.plugins.upload.services.upload;
const pm = strapi.admin.services.permission.createPermissionsManager(
userAbility,
ACTIONS.create,
fileModel
);
if (!pm.isAllowed) {
throw strapi.errors.forbidden();
}
const data = await validateUploadBody(body);
const file = await uploadService.upload({ data, files });
await uploadService.setCreatorInfo(user.id, file);
ctx.body = pm.sanitize(file, { action: ACTIONS.read });
},
};
const findEntityAndCheckPermissions = async (ability, action, model, id) => {
const file = await strapi.plugins.upload.services.upload.fetch(model, id);
if (_.isNil(file)) {
throw strapi.errors.notFound();
}
const pm = strapi.admin.services.permission.createPermissionsManager(ability, action, model);
if (pm.ability.cannot(pm.action, pm.toSubject(file))) {
throw strapi.errors.forbidden();
}
return { pm, file };
};

View File

@ -0,0 +1,109 @@
'use strict';
const _ = require('lodash');
const validateSettings = require('../validation/settings');
const validateUploadBody = require('../validation/upload');
module.exports = {
async find(ctx) {
const method = _.has(ctx.query, '_q') ? 'search' : 'fetchAll';
ctx.body = await strapi.plugins.upload.services.upload[method](ctx.query);
},
async findOne(ctx) {
const {
params: { id },
} = ctx;
const data = await strapi.plugins.upload.services.upload.fetch({ id });
if (!data) {
return ctx.notFound('file.notFound');
}
ctx.body = data;
},
async count(ctx) {
const method = _.has(ctx.query, '_q') ? 'countSearch' : 'count';
ctx.body = await strapi.plugins.upload.services.upload[method](ctx.query);
},
async destroy(ctx) {
const {
params: { id },
} = ctx;
const file = await strapi.plugins['upload'].services.upload.fetch({ id });
if (!file) {
return ctx.notFound('file.notFound');
}
await strapi.plugins['upload'].services.upload.remove(file);
ctx.body = file;
},
async updateSettings(ctx) {
const {
request: { body },
} = ctx;
const data = await validateSettings(body);
await strapi.plugins.upload.services.upload.setSettings(data);
ctx.body = { data };
},
async getSettings(ctx) {
const data = await strapi.plugins.upload.services.upload.getSettings();
ctx.body = { data };
},
async updateFileInfo(ctx) {
const {
query: { id },
request: { body },
} = ctx;
const data = await validateUploadBody(body);
ctx.body = await strapi.plugins.upload.services.upload.updateFileInfo(id, data.fileInfo);
},
async replaceFile(ctx) {
const {
id,
request: { body, files: { files } = {} },
} = ctx.query;
// cannot replace with more than one file
if (Array.isArray(files)) {
throw strapi.errors.badRequest(null, {
errors: [
{ id: 'Upload.replace.single', message: 'Cannot replace a file with multiple ones' },
],
});
}
ctx.body = await strapi.plugins.upload.services.upload.replace(id, {
data: await validateUploadBody(body),
file: files,
});
},
async uploadFiles(ctx) {
const {
request: { body, files: { files } = {} },
} = ctx.query;
ctx.body = await strapi.plugins.upload.services.upload.upload({
data: await validateUploadBody(body),
files,
});
},
};

View File

@ -0,0 +1,27 @@
'use strict';
const { yup, formatYupErrors } = require('strapi-utils');
const fileInfoSchema = yup.object({
name: yup.string().nullable(),
alternativeText: yup.string().nullable(),
caption: yup.string().nullable(),
});
const uploadSchema = yup.object({
fileInfo: fileInfoSchema,
});
const multiUploadSchema = yup.object({
fileInfo: yup.array().of(fileInfoSchema),
});
const validateUploadBody = (data = {}, isMulti = false) => {
const schema = isMulti ? multiUploadSchema : uploadSchema;
return schema.validate(data, { abortEarly: false }).catch(err => {
throw strapi.errors.badRequest('ValidationError', { errors: formatYupErrors(err) });
});
};
module.exports = validateUploadBody;

View File

@ -362,4 +362,21 @@ module.exports = {
})
.set({ value });
},
setCreatorInfo(userId, files, { edition = false } = {}) {
if (!Array.isArray(files)) files = [files];
const fields = { created_by: userId, updated_by: userId };
const creationFields = ['updated_by', 'created_by'];
const editionFields = ['updated_by'];
return Promise.all(
files.map(file => {
const params = _.pick(fields, edition ? editionFields : creationFields);
return strapi.query('file', 'upload').update({ id: file.id }, params);
})
);
},
};