Make locale/localizations private for non-localized cts (#21495)

This commit is contained in:
Jean-Sébastien Herbaux 2024-10-14 14:07:09 +02:00 committed by GitHub
parent e8787bcc0d
commit 7f39880265
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 19 deletions

View File

@ -3,6 +3,7 @@ import type { Core } from '@strapi/types';
import validateLocaleCreation from './controllers/validate-locale-creation';
import graphqlProvider from './graphql';
import { getService } from './utils';
export default ({ strapi }: { strapi: Core.Strapi }) => {
extendContentTypes(strapi);
@ -38,12 +39,16 @@ const addContentManagerLocaleMiddleware = (strapi: Core.Strapi) => {
* @param {Strapi} strapi
*/
const extendContentTypes = (strapi: Core.Strapi) => {
const { isLocalizedContentType } = getService('content-types');
Object.values(strapi.contentTypes).forEach((contentType) => {
const { attributes } = contentType;
const isLocalized = isLocalizedContentType(contentType);
_.set(attributes, 'locale', {
writable: true,
private: false,
private: !isLocalized,
configurable: false,
visible: false,
type: 'string',
@ -54,7 +59,7 @@ const extendContentTypes = (strapi: Core.Strapi) => {
relation: 'oneToMany',
target: contentType.uid,
writable: false,
private: false,
private: !isLocalized,
configurable: false,
visible: false,
unstable_virtual: true,

View File

@ -4,12 +4,14 @@ import localizations from './localizations';
import locales from './locales';
import isoLocales from './iso-locales';
import contentTypes from './content-types';
import sanitize from './sanitize';
export default {
permissions,
metrics,
localizations,
locales,
sanitize,
'iso-locales': isoLocales,
'content-types': contentTypes,
};

View File

@ -0,0 +1,42 @@
import type { Core, Schema, Data } from '@strapi/types';
import { traverseEntity } from '@strapi/utils';
import { curry } from 'lodash/fp';
import { getService } from '../../utils';
const LOCALIZATION_FIELDS = ['locale', 'localizations'];
const sanitize = ({ strapi }: { strapi: Core.Strapi }) => {
const { isLocalizedContentType } = getService('content-types');
/**
* Sanitizes localization fields of a given entity based on its schema.
*
* Remove localization-related fields that are unnecessary, that is
* for schemas that aren't localized.
*/
const sanitizeLocalizationFields = curry((schema: Schema.Schema, entity: Data.Entity) =>
traverseEntity(
({ key, schema }, { remove }) => {
const isLocalized = isLocalizedContentType(schema);
const isLocalizationField = LOCALIZATION_FIELDS.includes(key);
if (!isLocalized && isLocalizationField) {
remove(key);
}
},
{ schema, getModel: strapi.getModel.bind(strapi) },
entity
)
);
return {
sanitizeLocalizationFields,
};
};
type SanitizeService = typeof sanitize;
export default sanitize;
export type { SanitizeService };

View File

@ -4,6 +4,7 @@ import type { ContentTypesService } from '../services/content-types';
import type { MetricsService } from '../services/metrics';
import type { ISOLocalesService } from '../services/iso-locales';
import type { LocalizationsService } from '../services/localizations';
import type { SanitizeService } from '../services/sanitize';
type S = {
permissions: PermissionsService;
@ -12,6 +13,7 @@ type S = {
localizations: LocalizationsService;
['iso-locales']: ISOLocalesService;
['content-types']: ContentTypesService;
sanitize: SanitizeService;
};
const getCoreStore = () => {

View File

@ -1,10 +1,19 @@
'use strict';
const _ = require('lodash');
const { ApplicationError, ValidationError } = require('@strapi/utils').errors;
const { async, errors } = require('@strapi/utils');
const { getService } = require('../utils');
const { validateDeleteRoleBody } = require('./validation/user');
const { ApplicationError, ValidationError } = errors;
const sanitizeOutput = async (role) => {
const { sanitizeLocalizationFields } = strapi.plugin('i18n').service('sanitize');
const schema = strapi.getModel('plugin::users-permissions.role');
return async.pipe(sanitizeLocalizationFields(schema))(role);
};
module.exports = {
/**
* Default action.
@ -30,13 +39,17 @@ module.exports = {
return ctx.notFound();
}
ctx.send({ role });
const safeRole = await sanitizeOutput(role);
ctx.send({ role: safeRole });
},
async find(ctx) {
const roles = await getService('role').find();
ctx.send({ roles });
const safeRoles = await Promise.all(roles.map(sanitizeOutput));
ctx.send({ roles: safeRoles });
},
async updateRole(ctx) {

View File

@ -191,10 +191,9 @@ describe('Search query', () => {
expect(Array.isArray(res.body.results)).toBe(true);
expect(res.body.results.length).toBe(data.beds.length);
// TODO V5: Filter out i18n fields if content type is not localized
expect(res.body.results.map(omit([...CREATOR_FIELDS, 'localizations', 'status']))).toEqual(
expect.arrayContaining(data.beds)
);
expect(
res.body.results.map(omit([...CREATOR_FIELDS, 'locale', 'localizations', 'status']))
).toEqual(expect.arrayContaining(data.beds));
});
test('search with special characters', async () => {

View File

@ -26,7 +26,6 @@ const expectArticle = (letter: string) => {
return {
id,
title,
locale: 'en',
documentId: expect.any(String),
publishedAt: expect.anything(),
updatedAt: expect.anything(),
@ -108,24 +107,20 @@ const fixtures = {
category: (fixtures) => [
{
name: 'Category A',
locale: 'en',
tags: fixtures.tag.filter((tag) => tag.name.endsWith('B')).map((cat) => cat.id),
},
{
name: 'Category C',
locale: 'en',
tags: fixtures.tag
.filter((tag) => tag.name.endsWith('D') || tag.name.endsWith('A'))
.map((cat) => cat.id),
},
{
name: 'Category B',
locale: 'en',
tags: fixtures.tag.filter((tag) => tag.name.endsWith('C')).map((cat) => cat.id),
},
{
name: 'Category D',
locale: 'en',
tags: fixtures.tag.filter((tag) => tag.name.endsWith('A')).map((cat) => cat.id),
},
],
@ -134,24 +129,20 @@ const fixtures = {
return [
{
title: 'Article A',
locale: 'en',
categories: fixtures.category.filter((cat) => cat.name.endsWith('B')).map((cat) => cat.id),
},
{
title: 'Article C',
locale: 'en',
categories: fixtures.category.filter((cat) => cat.name.endsWith('D')).map((cat) => cat.id),
},
{
title: 'Article D',
locale: 'en',
categories: fixtures.category
.filter((cat) => cat.name.endsWith('A') || cat.name.endsWith('D'))
.map((cat) => cat.id),
},
{
title: 'Article B',
locale: 'en',
categories: fixtures.category
.filter((cat) => cat.name.endsWith('C') || cat.name.endsWith('D'))
.map((cat) => cat.id),

View File

@ -776,7 +776,6 @@ describe('Core API - Validate', () => {
// TODO: Sanitize id field
'id',
'documentId',
'locale',
'name',
'name_non_searchable',
'name_hidden',