Strapi v4 : Restreindre les droits des médias associés aux types de Contenu

Introduction

Lorsque vous utilisez Strapi comme système de gestion de contenu (CMS) pour votre application web ou votre site, l’une des fonctionnalités essentielles est la gestion des médias. Strapi permet aux utilisateurs de télécharger et de gérer différents types de fichiers multimédias, tels que des images, des vidéos et des documents.

Cependant, un problème potentiel se pose : tous les utilisateurs ont généralement les mêmes droits pour télécharger ou supprimer des fichiers, qu’ils soient ou non propriétaires des enregistrements associés. Par exemple, un utilisateur pourrait upload ou supprimer une image qui est liée à un enregistrement dont il n’est pas propriétaire, ce qui peut poser des problèmes de confidentialité et de sécurité.

Dans cet article, nous allons explorer comment restreindre les droits des médias associés aux Types de Contenu dans Strapi, en limitant la permission uniquement aux propriétaires des enregistrements des content types.

Prérequis

Avant de commencer, assurez-vous d’avoir installé Node.js et yarn. Vous pouvez installer Strapi globalement avec la commande suivante :

yarn create strapi-app my-project --ts --quickstart

Use case

Nous allons créer deux comptes utilisateurs pour notre cas d’utilisation afin de vérifier qu’avant notre mise en œuvre, le premier utilisateur a la possibilité de modifier l’avatar du deuxième utilisateur, et vice versa. L’objectif après notre mise en œuvre est de vérifier que chaque utilisateur ne peut modifier que son propre avatar.

Ajout d’un champ de type Media

Ajoutons par exemple un champ avatar au niveau de l’entité User dans Strapi Content-Types Builder > User. À l’intérieur de l’entité User, vous verrez les différents champs existants. Pour ajouter un champ image, cliquez sur le bouton “Add another field” ou “Ajouter un champ” s’il est en français comme ci dessous :

Configurer les permissions

  1. Allez dans la section “Settings” > “Roles”.
  2. Sélectionnez le rôle “Authenticated”.
  3. Assurez-vous que dans la section “User-Permissions > User”, la permission “update” est correctement sélectionnée.
  4. Vérifiez également que dans la section “Upload”, la permission “upload” et “destroy” sont bien sélectionnées.

Cela permettra aux utilisateurs de mettre à jour leurs informations, y compris l’upload et la suppression d’un avatar.

Remplissage des données

Nous allons procéder à la création des comptes utilisateurs de test. Pour cela, rendez-vous dans Content Manager > User, puis cliquez sur + Create New Entry.

Pour le deuxième compte utilisateur, procédez de la même manière. Dans mon cas, j’ai créé le compte [email protected] / 123456.

Tester

A l’état actuel des choses [email protected] est capable supprimer ou ajouter un avatar au compte de [email protected]. Nous pouvons vérifier cela en effectuant les actions ci dessous :

Commençons par récupérer le token de l’utilisateur [email protected]

Invoquons à présent notre endpoint /api/upload en tant que John afin de modifier l’avatar de Mamadou comme suit. N’oubliez pas d’inclure le jeton (token) obtenu depuis l’onglet Headers au niveau de Postman avec les valeurs suivantes :

  • Clé : Authorization
  • Valeur : Bearer JWT_TOKEN_JOHN

Implémentation de la solution

Nous allons mettre en place cette fonctionnalité en tenant compte de l’évolutivité et de l’organisation du code source dans notre projet Strapi. Aujourd’hui, l’implémentation concerne l’entité User, mais demain nous pourrions avoir un cas impliquant une entité différente.

Le type de contenu “User” est particulier car il est géré par le plugin natif plugin::users-permissions. Pour commencer, nous allons créer un service capable de nous retourner le propriétaire d’un enregistrement lorsqu’on lui passe l’ID. Bien que dans ce cas précis cela ne semble pas très pertinent car le champ “media” est directement lié à la table “User”, il est intéressant de réaliser cet exercice. En effet, cela pourrait s’avérer utile dans le cas d’une entité “Message” où le propriétaire serait l’utilisateur ayant soumis le message.

Users-permissions, tout comme le plugin d’upload, est une extension de Strapi. Pour implémenter notre fonctionnalité, nous allons étendre les fonctionnalités de ces plugins en créant les répertoires et fichiers suivants :

Etendre User-permissions :

Se placer à la racine du projet et saisir les commandes ci-dessous :

#Windows 
mkdir src\extensions\users-permissions\services
type nul > src\extensions\users-permissions\services\user.ts
type nul > src\extensions\users-permissions\strapi-server.ts

#MacOS 
mkdir -p src/extensions/users-permissions/services
touch src/extensions/users-permissions/services/user.ts
touch src/extensions/users-permissions/strapi-server.ts

Déposer le code ci dessous dans le fichier src/extensions/users-permissions/services/user.ts afin de créer notre service.

import { factories } from '@strapi/strapi';

export default factories.createCoreService('plugin::users-permissions.user', ({ strapi }) => ({
    async getUserOwner(id) {
        const user: any = await strapi.entityService.findOne(
            "plugin::users-permissions.user",
            id,
            { populate: [] }
        );

        return user;
    },
}));

Ensuite nous devons déclarer le service pour ce faire rajoutez le code ci-dessous dans le fichier src/extensions/users-permissions/strapi-server.ts :

import user from './services/user';

export default (plugin) => {
    plugin.services['userCustom'] = user;
    return plugin;
}

Etendre Upload

Se placer à la racine du projet et saisir les commandes ci-dessous :

#Windows 
mkdir src\extensions\upload
type nul > src\extensions\upload\strapi-server.ts

#MacOS 
mkdir -p src/extensions/upload
touch src/extensions/upload/strapi-server.ts

Pour restreindre les droits afin que chaque utilisateur puisse uniquement modifier son propre avatar, nous allons étendre les fonctionnalités du plugin Upload en ajoutant deux nouvelles méthodes : uploadCheck et destroyCheck. Ces méthodes vérifieront si l’utilisateur tente de modifier un document lié à une ressource qu’il possède. Si tel est le cas, la méthode appellera ensuite la méthode upload ou destroy en fonction de la situation.

Pour ce faire, placez le code ci-dessous dans le fichier créé précédemment src/extensions/upload/strapi-server.ts :

export default (plugin) => {

    plugin.controllers['content-api'].hasPermission = async (type, id, user) => {
        let granted = false;
        let owner;
        switch (type) {
            case 'plugin::users-permissions.user':
                owner = await strapi.service('plugin::users-permissions.userCustom').getUserOwner(id);
                if (owner.id == user.id) {
                    granted = true;
                }
                break;
            default:
                break;
        }

        return granted;
    }

    plugin.controllers['content-api'].destroyCheck = async (ctx) => {
        const {
            params: { id },
        } = ctx;
        const user = ctx.state.user;
        const file = await strapi.plugin('upload').service('upload').findOne(id, 'related');
        if (!file) {
            return ctx.notFound('file not found');
        }

        let canDelete = false;
        if (file.related.length == 1) {
            canDelete = await plugin.controllers['content-api'].hasPermission(file.related[0].__type, file.related[0].id, user);
        }

        if (canDelete) {
            await plugin.controllers['content-api'].destroy(ctx);
        }

        return {
            result: canDelete
        }
    }

    plugin.controllers['content-api'].uploadCheck = async (ctx) => {
        const body = ctx.request.body;
        const user = ctx.state.user;

        if (!body && body.length != 3) {
            return ctx.notFound('payload error');
        }

        let canCreate = await plugin.controllers['content-api'].hasPermission(body.ref, body.refId, user);
        if (canCreate) {
            await plugin.controllers['content-api'].upload(ctx);
        }

        return {
            result: canCreate
        }
    }

    plugin.routes['content-api'].routes.push(
        {
            method: 'DELETE',
            path: '/check/:id',
            handler: 'content-api.destroyCheck',
        },
        {
            method: 'POST',
            path: '/check',
            handler: 'content-api.uploadCheck',
        }
    );

    return plugin;
}

Maintenant, nous devons retirer les autorisations sur les API upload et destroy, car elles sont désormais contrôlées par nos méthodes uploadCheck et destroyCheck.

Tester

Pour tester, vous devez suivre la même procédure de test que précédemment, en pointant cette fois-ci vers le endpoint http://localhost:1337/api/upload/check pour l’upload.

Pour la suppression, vous devez appeler le endpoint suivant en utilisant la méthode DELETE. L’identifiant en paramètre correspond à l’identifiant de l’avatar que vous souhaitez supprimer : http://localhost:1337/api/upload/check/:id

Conclusion

En restreignant les droits des médias associés aux Types de Contenu dans Strapi, nous pouvons garantir une meilleure sécurité et confidentialité des données pour notre application. En suivant les étapes décrites ci-dessus et en utilisant les fonctionnalités de personnalisation et de contrôle d’accès de Strapi, nous pouvons mettre en place des politiques de sécurité efficaces qui permettent uniquement aux propriétaires d’enregistrements d’accéder et de gérer les médias associés.

Mamadou Diagne
Mamadou Diagne
Architecte logiciel & CTO

Diplômé d'ETNA, la filière d'alternance d'Epitech, j'ai acquis une expertise solide dans le développement d'applications, travaillant sur des projets complexes et techniquement diversifiés. Mon expérience englobe l'utilisation de divers frameworks et langages, notamment Symfony, Api Platform, Drupal, Zend, React Native, Angular, Vue.js, Shell, Pro*C...

0 0 votes
Évaluation de l'article
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires

Ingénierie informatique (SSII)

Applize crée des logiciels métiers pour accompagner les entreprises dans la transition vers le zéro papier.


Avez-vous un projet en tête ? Discutons-en.