lazurey / strapi-plugin-content-export-import

Strapi plugin for content export and import
166 stars 47 forks source link

Unable to give Author's permission to export content on Strapi #20

Open Ben888GitHub opened 3 years ago

Ben888GitHub commented 3 years ago

Hello Guys,

I want to enable Author to export their content on Strapi. As for now, it seems like only Admin can have that feature. I'm looking forward to your solution for this issue, thank you very much in advance.

Screen Shot 2021-03-09 at 7 33 43 PM Screen Shot 2021-03-09 at 7 33 46 PM Screen Shot 2021-03-09 at 8 01 33 PM
dappiu commented 3 years ago

I don't think the plugin has been built with different access roles in mind, but looking at this it seems that the plugin gets the available ContentTypes by making a GET request to /content-type-builder/content-types, so I think that you are not able to see any ContentType because of permissions related to ContentTypeBuilder plugin. You may start by allowing Author to call that route, but think to your security model because you're allowing the authors to have complete visibility of all you content-types names and perhaps more.

Ben888GitHub commented 3 years ago

@dappiu you meant including those content-types which are created by Admin as well?

Ben888GitHub commented 3 years ago

And in which part of Strapi should I go to enable the Author to call the route of /content-type-builder/content-types ?

brunomptavares commented 3 years ago

I would like to have the option to enable/disable the plugin for different roles. For example I don't want it to show up to the "Author" role. I would not mind doing it myself but I have no idea where to go... My first idea would be hacking into the left menu entries, but that looks ugly... Any hints?

brunomptavares commented 3 years ago

Well I just did it and it was not that hard!

First step is to go into the your-project/plugins/content-export-import/admin/src/index.js. From there locate the pluginsSectionLinks property. Because it is an array I suppose multiple objects could be there for multiple entries. For this plugin there is only one so I added the following property to the entry permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],.

Because we can't really edit the permissions for this plugin I presume it's not ready for it. I only want the plugin to show for the "Super Admin" role, and only this role has the content-type-builder plugin read permission. It's also the only example I found in the documentation so I think I was lucky! 😄

Also tried to create a policy and placed the string there but it did not work. It should be possible to do it better, according to the docs:

If the plugin has some permissions on whether or not it should be accessible depending on the logged in user's role you can set them here. Each permission object performs an OR comparison so if one matches the user's ones the link will be displayed

I tried some more stuff but could not make sense of this permission object. If anyone does please say so!

brunomptavares commented 3 years ago

I want to understand how the plugins work better and while researching other plugins to help @Ben888GitHub I found a way of doing what I wanted even cleaner:

1 - Let's create the permission at the file plugins\content-export-import\config\functions\bootstrap.js. Apparently this file runs every time the server starts up and it's used to setup some plugin related stuff, in this case it makes the plugin show up in the "Edit role" page.

'use strict';

module.exports = async () => {
  const actions = [
    {
      section: 'plugins',
      displayName: 'Read',
      uid: 'read',
      pluginName: 'content-export-import',
    },
  ];

  await strapi.admin.services.permission.actionProvider.registerMany(actions);
};

2 - Create the file plugins\content-export-import\admin\src\permissions.js and read the commentary for explanation of what it does. Basically returns the value of the permission we created before.

const pluginPermissions = {
  // This permission regards the main component (App) and is used to tell
  // If the plugin link should be displayed in the menu
  // And also if the plugin is accessible. This use case is found when a user types the url of the
  // plugin directly in the browser
  main: [{ action: 'plugins::content-export-import.read', subject: null }],
};

export default pluginPermissions;`

3 - Finally at the plugins\content-export-import\admin\src\index.js file import the permission and locate the menu property, where there is an array with the plugins entry for the menu pluginsSectionLinks.

import pluginPermissions from './permissions'
...
 menu: {
      pluginsSectionLinks: [
        {
          icon,
          name,
          destination: `/plugins/${pluginId}`,
          label: {
            id: `${pluginId}.plugin.name`,
            defaultMessage: name,
          },
          permissions: pluginPermissions.main,
        },
      ]
...

The result:

image

However I went through so much pain to find this and you can't really customize the plugins permissions for the roles. I think it must be a limitation for the Community Edition. However the "Super Admin" role is given all the permissions, unlike the other roles, which is exactly what I was looking for.

@Ben888GitHub I'm still up for your challenge and I think if we apply a policy (for example isAuthor to check if the role is "Author") on the /content-type-builder/content-types route we might be able to do what you are looking for. The thing is I could not make it work... yet!

brunomptavares commented 3 years ago

@Ben888GitHub This was an interesting challenge and for sure I learned a lot! Here are the steps you must do in order to be able to use this plugin with the Author role:

  1. Create the policy at extensions\content-type-builder\config\policies.isAuthor.js. Read more about extensions and policies.
module.exports = async (ctx, next) => {
  let triggerNext = true
  for (let role of ctx.state.admin.roles) {
    //Check the role name
    if (role.name === 'Author') {
      //We are not using next() to avoid going into the next policy
      //Let's call the controller directly from here based on the request url
      if(ctx.req.url === '/content-type-builder/content-types') {
        strapi.plugins['content-type-builder'].controllers.contenttypes.getContentTypes(ctx)
        triggerNext = false
      }
    }
  }
  // Go to next policy or will reach the controller's action.
  if (triggerNext) return await next();
};
  1. Copy node_modules\strapi-plugin-content-type-builder\config\routes.js into extensions\content-type-builder\config. Inside the file locate the /content-type-builder/content-types route and apply the policy we just created.
{
      "method": "GET",
      "path": "/content-types",
      "handler": "ContentTypes.getContentTypes",
      "config": {
        "policies": [
          "isAuthor",
          ["admin::hasPermissions", ["plugins::content-type-builder.read"]]
        ]
      }
    }

I think the strapi documentation it's not properly updated because I copied some code from the "Policies" page and they used ctx.state.admin.role which took a while to figure out. Now there is a roles array because an user can have multiple roles.

Basically we check the role name and if it matches then we check the url and immediatly call the corresponding controller (you can see the controller name in the handler property of the route).

The thing with the next() function is that will call the next policy (if there is one) or the controller. I think it's better that we keep the second policy and that's why "isAdmin" is at the beginning of the policies array. We only call the next() function if we did not call the controller directly.

Maybe the url check is not necessary because the policy is only applied to this specific route. I was thinking the plugin would need more than one route to work. If that was the case, the url check was need to call the appropriate controller.