payloadcms / payload

Payload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.
https://payloadcms.com
MIT License
28.33k stars 1.75k forks source link

Version publishing and draft permission weirdness #8350

Open LrsChrSch opened 2 months ago

LrsChrSch commented 2 months ago

Link to reproduction

No response

Environment Info

Binaries:
  Node: 20.11.1
  npm: N/A
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  payload: 3.0.0-beta.108
  next: 15.0.0-canary.160
  @payloadcms/db-postgres: 3.0.0-beta.108
  @payloadcms/email-nodemailer: 3.0.0-beta.108
  @payloadcms/graphql: 3.0.0-beta.108
  @payloadcms/next/utilities: 3.0.0-beta.108
  @payloadcms/plugin-cloud: 3.0.0-beta.108
  @payloadcms/richtext-lexical: 3.0.0-beta.108
  @payloadcms/translations: 3.0.0-beta.108
  @payloadcms/ui/shared: 3.0.0-beta.108
  react: 19.0.0-rc-5dcb0097-20240918
  react-dom: 19.0.0-rc-5dcb0097-20240918
Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Pro
  Available memory (MB): 49099
  Available CPU cores: 12

Describe the Bug

This is somewhat related to #6580

In my case I have a global with versions and drafts enabled. Sort of like this:

{
  slug: 'frontpage',
  fields: [
    ... // fields like title, etc.
  ],
  access: {
    read: publishedAccess, // basically _status === 'published'
    update: adminsAndModeratorsCanPublish, // see below
    readVersions: editorAccess // pretty much any logged in user
  },
  versions: {
    max: 10,
    drafts: {
      validate: true
    }
  }
}

adminsCanPublish looks like this (yes, very ugly but this will have to do for now):

export const adminsCanPublish: Access = ({ req: { user }, data }) => {
    if (user) {
        if (user.role === 'Admin') return true // admins can do whatever they want

        if (data?._status === 'published') return false // others will get blocked if they try to publish something

        return true // if they don't want to publish anything, let them through
    }

    return false;
}

This leads to following behaviour:

This behaviour is very confusing and probably not what was intended (except I did something wrong lol. in which case sorry for opening this issue and thanks for correcting my mistake :D )

I would rather expect the following behaviour:

I was able to somewhat bring this functionality back by adding a hook that just sets the _status to draft. This ofc breaks publishing completely, so it isn't a viable solution.

Afaik those publishing and draft buttons are visible based on an api test call. To fix this I'd propose modifying the canPublish api test call to use _status = 'draft' or doing one for the published status and for the draft status. Or something along those lines at least. Maybe a more finegrained access control with readVersions, canPublish and canDraft would be preferable in the future as that makes setting this up way easier to the user.

Thank you so much in advance! Maybe this isn't even a bug on the payload side but rather a setup mistake on mine. In any case, thanks for any help on this :)

Reproduction Steps

Adapters and Plugins

db-postgres

paulpopus commented 2 months ago

Hey, so I took your functions and simplified them a bit to explain it here, but I think this is an access control issue if I understand your problem correctly, correct me if I'm wrong.

access: {
  read: () => true,
  // Any user can create
  create: ({ req: { user } }) => {
    if (user) {
      return true
    }

    return false
  },
  update: ({ req: { user }, data }) => {
    if (user) {
      if (user.role === 'admin') {
        return true
      } // admins can do whatever they want

      if (data?._status === 'published') {
        return false
      } // others will get blocked if they try to publish something

      return true // if they don't want to publish anything, let them through
    }

    return false
  },

  // Only admins can delete
  delete: ({ req: { user } }) => {
    if (user) {
      if (user.role === 'admin') {
        return true
      }
    }

    return false
  },
},

So with these functions, I tested the following workflow:

So editors can continue editing copies of the published document but they cannot edit the actual published version if that makes sense.

In my code I did allow creation for any user, not sure if that was missing from your access control or not.

Does this address your issue here?

LrsChrSch commented 2 months ago

Hi! Thanks for your reply!

Right, the editors can't edit the published version (which in itself is fine). The problem is that as soon as a version is published when an editor visits the page, all the fields will be greyed out like you said. So editors can't make changes without going into the versions first and reverting a version.

And that's what confused me a bit. The editors should be able to edit any versions, but they should only be able to save them as drafts. That would mean the fields should stay editable and the "save Draft" button should also be visible even when a version was recently published.

Hope that made sense, but just in case I added a few screenshots at the bottom.

For example, this is the first version that was created and published by the admin:

Screenshot 2024-09-23 154049

The editors view looks like this (note that they can't publish anything new which is good. but they also can't save a new draft):

Screenshot 2024-09-23 154056

This is after the admin has created a new draft (version 3 in this case):

Screenshot 2024-09-23 154226

And this is what the editor view looks like when that draft is the most recent document. They can't publish anything, but they are suddenly allowed to save the draft again (if they made any changes ofc).

Screenshot 2024-09-23 154232

What editors can also do is to revert a published version (ignore the rest, version ID 8 is a reverted version of version 2.). That means editors are allowed to make a new version with _status = 'published'. That ofc only creates a copy of it so it at least isn't a security concern.

Screenshot 2024-09-23 154727

They can however edit this new copy all of a sudden (even though it's technically published) and they can save it as a draft.

Screenshot 2024-09-23 154741

Hope that helped with clarifying my initial confusing post a bit. Again: Thank you so much for your help!

paulpopus commented 2 months ago

They can however edit this new copy all of a sudden (even though it's technically published) and they can save it as a draft.

Yeah that's intentional since drafts don't have status published but editors will always be able to edit or create drafts even from the published document.

If you need additional ways to lock down content you can use a checkbox or a custom status field as well that you can check against to ensure that editors can never edit or create more draft versions of it further

LrsChrSch commented 2 months ago

Ah, okay. But then why would the "Save Draft" button be missing in the second screenshot? I get that editors shouldn't be able to edit the published version directly. But why not always show the Button and create a new draft with the edits made to the published version?

That would keep users from going into the versions tab, manually creating a new version and editing that.