1

I have generated a custom entity using Drupal Console.

drupal generate:entity:content

The entity doesn't have the permission edit own content which we usually have in node types etc... The entities entity_name.permissions.yml the file has the following enteries.

add company entities:
  title: 'Create new Company entities'

administer company entities:
  title: 'Administer Company entities'
  description: 'Allow to access the administration form to configure Company entities.'
  restrict access: true

delete company entities:
  title: 'Delete Company entities'

edit company entities:
  title: 'Edit Company entities'

access company overview:
  title: 'Access the Company overview page'

view published company entities:
  title: 'View published Company entities'

view unpublished company entities:
  title: 'View unpublished Company entities'

I tried understanding, how this is done in node. But in node types permission file also I couldn't find this permission entered.

1
  • 1
    The generator is just a starting point. You could just add your own permissions in after the file is generated. You could also just add a permission callback in permissions.yml to dynamically generate permissions too.
    – Kevin
    Commented Jul 1, 2017 at 18:08

6 Answers 6

3

Like other content entities, the Node entity has an access control class, NodeAccessControlHandler, whose purpose is giving users the permission to view, edit, delete (in NodeAccessControlHandler::access()), or create a node (in NodeAccessControlHandler::createAccess()).
NodeAccessControlHandler::checkAccess() (indirectly called from NodeAccessControlHandler::access()) uses the values returned from hook_node_grants() and hook_node_access_records() to extend or restrict the permissions users have on a node.

NodePermissions::nodeTypePermissions(), which is added in the node.permissions.yml file as permission callback, defines additional permissions for the content types.
That method calls NodePermissions::buildPermissions(), which returns an array of permissions.

  return [
    "create {$type_id} content" => [
      'title' => $this->t('%type_name: Create new content', $type_params),
    ],
    "edit own {$type_id} content" => [
      'title' => $this->t('%type_name: Edit own content', $type_params),
    ],
    "edit any {$type_id} content" => [
      'title' => $this->t('%type_name: Edit any content', $type_params),
    ],
    "delete own {$type_id} content" => [
      'title' => $this->t('%type_name: Delete own content', $type_params),
    ],
    "delete any {$type_id} content" => [
      'title' => $this->t('%type_name: Delete any content', $type_params),
    ],
    "view {$type_id} revisions" => [
      'title' => $this->t('%type_name: View revisions', $type_params),
      'description' => t('To view a revision, you also need permission to view the content item.'),
    ],
    "revert {$type_id} revisions" => [
      'title' => $this->t('%type_name: Revert revisions', $type_params),
      'description' => t('To revert a revision, you also need permission to edit the content item.'),
    ],
    "delete {$type_id} revisions" => [
      'title' => $this->t('%type_name: Delete revisions', $type_params),
      'description' => $this->t('To delete a revision, you also need permission to delete the content item.'),
    ],
  ];

The Node module has also its own hook_ENTITY_TYPE_access(), node_node_access(). That hook changes which users are allowed to update or delete a node.

What the Node module does to give access to nodes is probably not the best example to follow when creating a new entity. The Node module uses hook_node_grants() and hook_node_access_records() for backward compatibility; new entity classes could just use an access handler that implements the access() and checkAccess() methods, and eventually define a permission handler in the .permission.yml file.

3

A little surprised how easy this was to fix; but it seems to be working.

Console originally created permissions similar to what @esefwan posted and in MyEntityAccessConrtolHandler.php it created this code:

  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
    /** @var \Drupal\reserve\Entity\MyEntityInterface $entity */
    switch ($operation) {
      case 'view':
        if (!$entity->isPublished()) {
          return AccessResult::allowedIfHasPermission($account, 'view unpublished myentity');
        }
        return AccessResult::allowedIfHasPermission($account, 'view published myentity');

      case 'update':
        return AccessResult::allowedIfHasPermission($account, 'edit myentity entities');

      case 'delete':
        return AccessResult::allowedIfHasPermission($account, 'delete myentity entities');
    }

    // Unknown operation, no opinion.
    return AccessResult::neutral();
  } 

Looking at the code in node.module under node_node_access() i ended up modifying the above to this:

  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
    /** @var \Drupal\reserve\Entity\ReserveReservationInterface $entity */
    switch ($operation) {
      case 'view':
        if (!$entity->isPublished()) {
          $access = AccessResult::allowedIfHasPermission($account, 'view unpublished myentity');
        }
        $access = AccessResult::allowedIfHasPermission($account, 'view published myentity');
        break;

      case 'update':
        $access = AccessResult::allowedIfHasPermission($account, 'edit any myentity');
        if (!$access->isAllowed() && $account->hasPermission('edit own myentity')) {
          $access = $access->orIf(AccessResult::allowedIf($account->id() == $entity->getOwnerId())->cachePerUser()->addCacheableDependency($entity));
        }
        break;

      case 'delete':
        $access =  AccessResult::allowedIfHasPermission($account, 'delete any myentity');
        break;

      // Unknown operation, no opinion.
      default:
        $access = AccessResult::neutral();
    }

    return $access;
  }
3
  • And did this work for you? Is the code tested?
    – esafwan
    Commented Jun 26, 2019 at 22:26
  • Been a while now but I think it worked. You could find it in this project: drupal.org/project/reserve
    – liquidcms
    Commented Jun 28, 2019 at 14:08
  • I tested it and it worked fine. please see my answer below. Commented Jan 11, 2022 at 11:14
1

I'm not sure if Drupal Console has been updated since you asked the question, but what you'll find now is that it defines a set of permissions, but also adds an annotation to the class that defines your entity, something like this:

  handlers = {...
    "access" = "Drupal\my_module\MyModuleAccessControlHandler"
    ...
  }

The AccessControlHandler file will be in your src directory and if you add permissions in the permissions.yml file, you can edit responses to those, using the node_node_access function as an example, as explained in Milos's comment.

1

I tested this in Drupal 9.3.0 (View, Edit and Delete operations):

  /**
   * {@inheritdoc}
   */
  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
    /** @var \Drupal\password_locker_entities\Entity\PasswordInterface $entity */

    switch ($operation) {
      case 'view':
        $access = AccessResult::allowedIfHasPermission($account, 'view any password entities');
        if (!$access->isAllowed() && $account->hasPermission('view own password entities')) {
          $access = $access->orIf(AccessResult::allowedIf($account->id() == $entity->getOwnerId())->cachePerUser()->addCacheableDependency($entity));
        }
        break;

      case 'update':
        $access = AccessResult::allowedIfHasPermission($account, 'edit any password entities');
        if (!$access->isAllowed() && $account->hasPermission('edit own password entities')) {
          $access = $access->orIf(AccessResult::allowedIf($account->id() == $entity->getOwnerId())->cachePerUser()->addCacheableDependency($entity));
        }
        break;

      case 'delete':
        $access =  AccessResult::allowedIfHasPermission($account, 'delete any password entities');
        if (!$access->isAllowed() && $account->hasPermission('delete own password entities')) {
          $access = $access->orIf(AccessResult::allowedIf($account->id() == $entity->getOwnerId())->cachePerUser()->addCacheableDependency($entity));
        }
        break;

      // Unknown operation, no opinion.
      default:
        $access = AccessResult::neutral();
    }

    return $access;
  }
0

I added two additional permissions in my permissions.yml file (view own and edit own...) and altered the checkAccess by adding the following conditions in the switches. Everything was initially created by drush generate.

  /**
   * {@inheritdoc}
   */
  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {

    $uid = $entity->getOwnerId();

    switch ($operation) {
      case 'view':
        if ($account->id() != $uid) {
          return AccessResult::allowedIfHasPermission($account, 'view company');
        }
        elseif ($account->id() == $uid) {
          return AccessResult::allowedIfHasPermissions($account, ['view company, view own company'], 'OR');
        }
        break;
      case 'update':
        if ($account->id() != $uid) {
          return AccessResult::allowedIfHasPermissions($account, ['edit company', 'administer company'], 'OR');
        }
        elseif ($account->id() == $uid) {
          return AccessResult::allowedIfHasPermissions($account, ['edit own company', 'administer company', 'edit company'], 'OR');
        }
        break;
      case 'delete':
        return AccessResult::allowedIfHasPermissions($account, ['delete company', 'administer company'], 'OR');

      default:
        // No opinion.
        return AccessResult::neutral();
    }

  }```
0

On custom entities, you usually would want something like "create / edit / delete" own entities, with access purely based on own entities. To do this, check the

modules/custom/mymodule/mymodule.permissions.yml with the following:

administer own mycustomentityname:
 title: 'Create/edit/delete own mycustomentityname'

Then in your src/MyEntityAccessHandler.php checkAccess method:

 case 'edit':
   if ($account->hasPermission('administer own mycustomentityname')) {
     $access = AccessResult::allowedIf($account->id() == $entity->getOwnerId())->cachePerUser()->addCacheableDependency($entity);
   }

Source: Start with custom entities & Learn Drupal as a framework

It is nice to have a entity generator like drush does, but from my experience you can set it up with less files quickly and you have more control.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.