make file signing non mutating

This commit is contained in:
Marc-Roig 2023-02-11 17:37:28 +01:00
parent 67271d2026
commit 11c4b9df3a
4 changed files with 63 additions and 62 deletions

View File

@ -1,6 +1,7 @@
'use strict';
const { merge } = require('lodash/fp');
const { mapAsync } = require('@strapi/utils');
const { getService } = require('../utils');
const { ACTIONS, FILE_MODEL_UID } = require('../constants');
const { findEntityAndCheckPermissions } = require('./utils/find-entity-and-check-permissions');
@ -25,14 +26,14 @@ module.exports = {
const query = pm.addPermissionsQueryTo(merge(defaultQuery, ctx.query));
const { results, pagination } = await getService('upload').findPage(query);
const { results: files, pagination } = await getService('upload').findPage(query);
const fileService = await getService('file');
await Promise.all(results.map((file) => fileService.signFileUrls(file)));
// Sign file urls for private providers
const signedFiles = await mapAsync(files, getService('file').signFileUrls);
const sanitizedResults = await pm.sanitizeOutput(results);
const sanitizedFiles = await pm.sanitizeOutput(signedFiles);
return { results: sanitizedResults, pagination };
return { results: sanitizedFiles, pagination };
},
async findOne(ctx) {

View File

@ -17,7 +17,7 @@ module.exports = async ({ strapi }) => {
await registerUploadMiddleware({ strapi });
getService('file').addSignedFileUrlsToAdmin();
getService('extensions').contentManager.entityManager.addSignedFileUrlsToAdmin();
if (strapi.plugin('graphql')) {
require('./graphql')({ strapi });

View File

@ -3,54 +3,59 @@
const { mapAsync } = require('@strapi/utils');
const { getService } = require('../../../utils');
const getSignedAttribute = (attributeName, entity, model) => {
const { signFileUrls } = getService('file');
if (!entity) return entity;
const attribute = model.attributes[attributeName];
switch (attribute?.type) {
case 'media':
if (attribute.multiple) {
return mapAsync(entity, signFileUrls);
}
return signFileUrls(entity);
case 'component':
if (attribute.repeatable) {
return mapAsync(entity, (component) => signEntityMedia(component, attribute.component));
}
return signEntityMedia(entity, attribute.component);
case 'dynamiczone':
return mapAsync(entity, (component) => signEntityMedia(component, component.__component));
default:
return entity;
}
};
/**
*
* Iterate through an entity manager result
* Check which modelAttributes are media and pre sign the image URLs
* if they are from the current upload provider
*
* TODO: Create a strapi-utils function to iterate through an entity
* and apply a function on each attribute type
*
* @param {Object} entity
* @param {Object} modelAttributes
* @param {String} providerConfig
* @returns
*/
const signEntityMedia = async (entity, uid) => {
const model = strapi.getModel(uid);
const { signFileUrls } = getService('file');
for (const [key, value] of Object.entries(entity)) {
// eslint-disable-next-line no-continue
if (!value) continue;
const signedEntity = {};
const attribute = model.attributes[key];
switch (attribute?.type) {
case 'media':
if (attribute.multiple) {
await mapAsync(value, signFileUrls);
} else {
await signFileUrls(value);
}
break;
case 'component':
if (attribute.repeatable) {
await Promise.all(
value.map((component) => signEntityMedia(component, attribute.component))
);
} else {
await signEntityMedia(value, attribute.component);
}
break;
case 'dynamiczone':
await Promise.all(
value.map((component) => signEntityMedia(component, component.__component))
);
break;
default:
break;
}
// TODO: Use asyncReduce
for (const attributeName of Object.keys(entity)) {
signedEntity[attributeName] = await getSignedAttribute(
attributeName,
entity[attributeName],
model
);
}
return entity;
return signedEntity;
};
const addSignedFileUrlsToAdmin = () => {
@ -67,46 +72,39 @@ const addSignedFileUrlsToAdmin = () => {
// - What about the webhooks emitted by the entity manager?
// - Do we want to sign the file urls in the event payload?
// Test for every case in the Content manager so we don't miss any
// Make entity file signing non mutating
// Move this extend into a folder called /extensions
// Can we simplify the way to extend the content manager?
// TOPICS:
// What about the webhooks emitted by the entity manager?
// Do we want to sign the file urls in the event payload?
// We need to do this for create/update/delete/publish/unpublish too no?
// Documentation
strapi.container
.get('services')
.extend(`plugin::content-manager.entity-manager`, (entityManager) => {
const update = async (entity, body, uid) => {
const updatedEntity = await entityManager.update(entity, body, uid);
await signEntityMedia(updatedEntity, uid);
return updatedEntity;
return signEntityMedia(updatedEntity, uid);
};
const publish = async (entity, body, uid) => {
const publishedEntity = await entityManager.publish(entity, body, uid);
await signEntityMedia(publishedEntity, uid);
return publishedEntity;
return signEntityMedia(publishedEntity, uid);
};
const unpublish = async (entity, body, uid) => {
const unpublishedEntity = await entityManager.unpublish(entity, body, uid);
await signEntityMedia(unpublishedEntity, uid);
return unpublishedEntity;
return signEntityMedia(unpublishedEntity, uid);
};
const findOneWithCreatorRolesAndCount = async (id, uid) => {
// TODO: What if the entity is not found?
const entity = await entityManager.findOneWithCreatorRolesAndCount(id, uid);
await signEntityMedia(entity, uid);
return entity;
return signEntityMedia(entity, uid);
};
const findWithRelationCountsPage = async (opts, uid) => {
const entities = await entityManager.findWithRelationCountsPage(opts, uid);
await mapAsync(entities.results, async (entity) => signEntityMedia(entity, uid));
return entities;
const results = await mapAsync(entities.results, async (entity) =>
signEntityMedia(entity, uid)
);
return { ...entities, results };
};
return {

View File

@ -1,5 +1,6 @@
'use strict';
const { cloneDeep } = require('lodash/fp');
const { mapAsync } = require('@strapi/utils');
const { FOLDER_MODEL_UID, FILE_MODEL_UID } = require('../constants');
const { getService } = require('../utils');
@ -24,20 +25,17 @@ const deleteByIds = async (ids = []) => {
const signFileUrl = async (fileIdentifier) => {
const { provider } = strapi.plugins.upload;
const { url } = await provider.getSignedUrl(fileIdentifier);
return url;
};
// TODO: Make this non mutating?
const signFileUrls = async (file) => {
const { provider } = strapi.plugins.upload;
const { provider: providerConfig } = strapi.config.get('plugin.upload');
// Check file provider and if provider is private
if (file.provider !== providerConfig || !provider.isPrivate()) {
return;
return file;
}
const signUrl = async (file) => {
@ -45,11 +43,15 @@ const signFileUrls = async (file) => {
file.url = signedUrl.url;
};
const signedFile = cloneDeep(file);
// Sign each file format
await signUrl(file);
await signUrl(signedFile);
if (file.formats) {
await mapAsync(Object.values(file.formats), signUrl);
await mapAsync(Object.values(signedFile.formats), signUrl);
}
return signedFile;
};
module.exports = {