Access Control RBAC - get record ID / get collection

default discord avatar
ryanlanciaux2 years ago
2 1

I'm trying to follow along with the docs and receive an ID with my access control functions.

I'm expecting the req and id parameter, but am only seeing req. It looks like the URL is /access and not the collection I'm checking (This is when loading the update page).

Additionally, is there any way to figure out what collection I'm in on an access control method? I can see the user, but I'd also like to do more advanced checks depending on which collection I'm on.

  • Selected Answer
    discord user avatar
    jmikrut
    2 years ago

    Hey @ryanlanciaux — I can give some insight here.

    The id parameter is optional and only going to be present in some access control methods, in certain cases. For example, there is no id yet in create access control.

    Also—you're seeing your access control function being called from the access operation, which goes through and calls all access control through all your collections and globals. But, while your access control functions are being called by the access operation, there will be no ID present there either.

    The access operation is responsible for telling the admin UI what you can and can't do. You can hit it in your browser by going to /api/access to see what it looks like.

    Also, in regards to knowing what collection you're in, I would recommend abstracting your access control functions in a way that allows you to pass them the collection that you're on so you have full control. Like this:

    export const accessControl = ({ req }) => {
      // how do we know what collection we're in?
      return true; // simple example
    }
    import { accessControl } from './access';
    
    const pages = {
      slug: 'pages',
      access: {
        read: accessControl,
      }
    }

    You're probably sharing a function like this through many collections, and within that function there is no way to know what collection you're on.

    Instead, you could do this:

    export const getAccessControl = (collectionSlug) => ({ req }) => {
      // Now we know that our collection is `collectionSlug`
      return true; // simple example
    }
    import { getAccessControl } from './access';
    
    const pages = {
      slug: 'pages',
      access: {
        read: getAccessControl('pages'),
      }
    }

    Does that make sense? There are many other patterns of abstraction that can work here but this is the one we'd generally use.

    Also - can I ask what you need the ID of the document for? Are you familiar with the nature of how returning query constraints can help you write super clean access control functions? In about 99% of cases you can do what you need with returning a query constraint instead of dealing directly with a document ID.

    👍

    3 replies
  • default discord avatar
    ryanlanciaux2 years ago

    Thanks, I think it makes sense. In regard to the ID, I may need to describe the configuration I'm currently trying to have admins, and organization admins.

    Collections

    // kind of pseudocode - not entirely valid
    const Users =  {
      role: 'Admin' | 'OrganizationAdmin',
      // conditional based on admin or organizationAdmin
      organizations: { type: 'relationship', hasMany: true, name: 'organizations', relationTo: ['organizations']  }
    }
    
    const Organizations = {
       ... fields for organizations, there are some blocks here too (most of my confusion is here)
    }
    

    Attempting to use query

    Against organizations, when I write a query, it always is showing up. Initially I attempted to handle this with a query somewhat similar to the following (accepting user off of req)

      return {
          id: {
            in: userOrganizationIds // array
          },
      }
    
    

    This always provides access to update the fields when I'm expecting it should be like returning false from the method.

    Manually attempting to query

    This is where I'm trying to obtain the ID and the collection I'm in. It feels like an unideal way to achieve the result I'm looking for, but the thinking is I could manually perform the query with the payload.find and return true / false depending on the results.

  • discord user avatar
    jmikrut
    2 years ago

    OK I'm following what you're doing.

    I think it would be good to see your whole access control function code. This may just be a case of making sure that you are reducing down your req.user.organizations to their IDs only.

    Take a look at the following documentation regarding how relationship data is saved:

    https://payloadcms.com/docs/fields/relationship#how-the-data-is-saved

    You'll see that because you have your relationTo property set to an array of values, your data will be saved as the Has Many - Polymorphic section from the docs. Like this:

    const user = {
      // the rest of the user here
      organizations: [
        {
          relationTo: 'organizations',
          value: '34jl34ij53l4ijrl34ijr43', // some ID
        },
        {
          relationTo: 'organizations',
          value: '34jl34ij53l4ijrl34ijr43', // some ID
        },    
      ]
    }

    Which means that your access control should be formatted like this:

    export const restrictByOrganization = ({ req }) => {
      if (req.user && Array.isArray(req.user.organizations)) {
        // As you are saving your user orgs as `hasMany: true` and `relationTo` as an array,
        // we need to reduce down the user organizations to only their values
        const orgIDs = req.user.organizations.reduce((ids, org) => {
          return [
            ...ids,
            org.value,
          ]
        }, []);
    
        // Now we'll end up with an array of IDs appropriately
        // and can use the IDs to build a query constraint
    
        return {
          id: {
            in: orgIDs
          }
        }
      }
    
      return false;
    };

    Is this where your problem lies? Are you sure you're returning an array of IDs only?

  • default discord avatar
    ryanlanciaux2 years ago

    Thanks a ton - This works. I think reducing the values made a difference as opposed to trying to query the items directly.

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.