Problem/Motivation

There is a use-case where you need per-form access control

Proposed resolution

Add a 'protect' option to the contact form third party settings.
Add a new permission for each contact form with 'protect' = TRUE
Add a new access controller for viewing contact_form entities
Use the base permission for those with protect = FALSE and the specific one for those with protect = TRUE
Add a new test
Make it in a sub-module.

Remaining tasks

Patch
Tests
Review

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

larowlan created an issue. See original summary.

larowlan’s picture

Title: Role based access to forms » Per form based access permissions
larowlan’s picture

Issue tags: +drupalcon, +sprint
berdir’s picture

+1 to submodule. Should possibly be a setting per form (to be protected or not) and default to public, similar to how field permissions work, otherwise it's always complicated to add new forms.

entity.contact_form.canonical route uses contact_form.view entity access, so we can hook into that easily.

larowlan’s picture

Agree

These two are related too.

Given this is a sub-module, those would be part of the main module.

This one would need to work with them so I'm guessing a sub controller or an alter hook.

#2724507: Add a maximum submission limit for forms
#2724499: Allow disabling forms to prevent new submissions

naveenvalecha’s picture

I have worked on the implementation of this functionality.
https://www.drupal.org/sandbox/naveenvalecha/2725179
Following items are done :

  • Add a 'protect' option to the contact form third party settings.
  • Add a new permission for each contact form with 'protect' = TRUE
  • Add a new access controller for viewing contact_form entities
  • Use the base permission for those with protect = FALSE and the specific one for those with protect = TRUE

Pending action items :

  • entity.contact_form.canonical route uses contact_form.view entity access, so we can hook into that easily.
  • Add a new test
  • Make it in a sub-module.
naveenvalecha’s picture

Status: Active » Needs review
StatusFileSize
new9.35 KB

Here it goes the test with following features in patch :

  • Add a 'protect' option to the contact form third party settings.
  • Add a new permission for each contact form with 'protect' = TRUE
  • Add a new access controller for viewing contact_form entities
  • Use the base permission for those with protect = FALSE and the specific one for those with protect = TRUE
  • Add a new test
  • Make it in a sub-module.

Pending :

  • entity.contact_form.canonical route uses contact_form.view entity access, so we can hook into that easily.

Questions :
Do we need a separate Readme.txt for the sub module ?

naveenvalecha’s picture

larowlan’s picture

Thanks @naveenvalecha, looking good - a few comments/observations

  1. +++ b/modules/contact_permissions/contact_permissions.info.yml
    @@ -0,0 +1,7 @@
    +description: Provides permissions for having a personal contact form
    
    +++ b/modules/contact_permissions/contact_permissions.module
    @@ -0,0 +1,60 @@
    +      $output .= '<p>' . t('Provides permissions for having a personal contact form') . '</p>';
    

    Could we instead say 'Provides the ability to add a new permission to each contact form to limit access' or something. Not really to do with personal contact form.

  2. +++ b/modules/contact_permissions/contact_permissions.info.yml
    @@ -0,0 +1,7 @@
    +  - contact
    +  - contact_storage
    

    I think just listing contact_storage is enough

  3. +++ b/modules/contact_permissions/contact_permissions.module
    @@ -0,0 +1,60 @@
    + * Contains contact_permissions.module..
    

    nit - two .

  4. +++ b/modules/contact_permissions/contact_permissions.module
    @@ -0,0 +1,60 @@
    +    '#title' => t('Make Protected'),
    

    I think we should add a #description here to explain what this options does. Something like 'Require a unique permission to access this form'

  5. +++ b/modules/contact_permissions/contact_permissions.permissions.yml
    @@ -0,0 +1,2 @@
    \ No newline at end of file
    

    nit: no newline

  6. +++ b/modules/contact_permissions/src/ContactFormAccessControlHandler.php
    @@ -0,0 +1,41 @@
    +      else {
    +        // Do not allow access personal form via site-wide route.
    +        return AccessResult::allowedIf($account->hasPermission('access site-wide contact form') && $entity->id() !== 'personal')->cachePerPermissions();
    +      }
    +    }
    +    elseif ($operation == 'delete' || $operation == 'update') {
    +      // Do not allow the 'personal' form to be deleted, as it's used for
    +      // the personal contact form.
    +      return AccessResult::allowedIf($account->hasPermission('administer contact forms') && $entity->id() !== 'personal')->cachePerPermissions();
    +    }
    

    We could defer this to the parent implementation

  7. +++ b/modules/contact_permissions/src/Tests/ContactAccessTest.php
    @@ -0,0 +1,76 @@
    +   * An administrative user with permission administer contact forms.
    

    nit: permission *to* administer

  8. +++ b/modules/contact_permissions/src/Tests/ContactAccessTest.php
    @@ -0,0 +1,76 @@
    +    $this->drupalGet('contact/test_id');
    

    Can we assertResponse(200) after this

  9. +++ b/modules/contact_permissions/src/Tests/ContactAccessTest.php
    @@ -0,0 +1,76 @@
    \ No newline at end of file
    

    nit: newline

naveenvalecha’s picture

StatusFileSize
new9.48 KB
new3.11 KB

#9.1 Done
#9.2 Done
#9.3 Done
#9.4 Done
#9.5 Done
#9.7 Done
#9.8 Done
#9.9 Done

berdir’s picture

Status: Needs review » Needs work
+++ b/modules/contact_permissions/src/ContactFormAccessControlHandler.php
@@ -0,0 +1,41 @@
+    elseif ($operation == 'delete' || $operation == 'update') {
+      // Do not allow the 'personal' form to be deleted, as it's used for
+      // the personal contact form.
+      return AccessResult::allowedIf($account->hasPermission('administer contact forms') && $entity->id() !== 'personal')->cachePerPermissions();
+    }

this is already in the parent, doesn't need to be duplicated.

If don't really like overriding the access control handler. Online one module can do this, whenever possible, we should work with the access hooks.

That means for this, we need to do a allow or forbidden, not allowedIf().

jibran’s picture

Patch looks good to me. @naveenvalecha can you please address #10 so that we can move forward here.

naveenvalecha’s picture

Status: Needs work » Needs review
StatusFileSize
new9.16 KB
new0 bytes

Accommodated #11

//Naveen

naveenvalecha’s picture

StatusFileSize
new1.27 KB

Here's the correct inter diff.

//Naveen

Status: Needs review » Needs work

The last submitted patch, 13: 2724503-13.patch, failed testing. View results

naveenvalecha’s picture

Status: Needs work » Needs review
StatusFileSize
new9.15 KB
new804 bytes

Here's the green patch.

Running the WTB on local is damn slow. Filed the task #2886713: Convert Tests to BTB from WTB to convert the tests to BTB

jibran’s picture

Assigned: Unassigned » berdir

Over to @berdir

naveenvalecha’s picture

StatusFileSize
new9.21 KB
new790 bytes

This landed #2886713: Convert Tests to BTB from WTB
Here's the updated patch. New test is damn fast compared to before.

$ ../vendor/bin/phpunit --filter testContactViewBuilder ../modules/contrib/contact_storage/modules/contact_permissions/tests/src/Functional/ContactAccessTest.php 
PHPUnit 4.8.35 by Sebastian Bergmann and contributors.

.

Time: 41.07 seconds, Memory: 6.00MB

OK (1 test, 10 assertions)
foolfitz’s picture

Excuse me, personal contact form has no options page, how to set the 'protect' option to it?

Thank you for the great job.

hanness’s picture

Did anyone looked at the module Contact Message Permissions?

It claims to be An extension of the core contact module that provides form-by-form permissions to access (edit/view/delete) message entities generated by these forms.

matt_paz’s picture

Not sure if this patch will ever make it in, but if so, D9 compatibility should be added to contact_permissions.info.yml

larowlan’s picture

Status: Needs review » Needs work
  1. +++ b/modules/contact_permissions/contact_permissions.module
    @@ -0,0 +1,61 @@
    +  $entity_types['contact_form']->setAccessClass('\Drupal\contact_permissions\ContactFormAccessControlHandler');
    

    we also need to switch the permissions route provider from EntityPermissionsRouteProviderWithCheck to EntityPermissionsRouteProvider

  2. +++ b/modules/contact_permissions/src/ContactPermissions.php
    @@ -0,0 +1,55 @@
    +class ContactPermissions {
    

    This needs to use \Drupal\Core\Entity\BundlePermissionHandlerTrait now so that we get the correct dependencies

majorrobot made their first commit to this issue’s fork.

majorrobot’s picture

Status: Needs work » Needs review

This seems like a good feature to replace the Contact Form Permissions module (which appears abandoned and doesn't have a supported release anymore). So I'm excited to see it in progress!

I've created a fork/MR from @naveenvalecha's patch in #18 and updated core version requirements.

I've also attempted to address @larowlan's requests in #22.

+++ b/modules/contact_permissions/src/ContactPermissions.php
@@ -0,0 +1,55 @@
+class ContactPermissions {
This needs to use \Drupal\Core\Entity\BundlePermissionHandlerTrait now so that we get the correct dependencies

I've added this, as I understand it -- but this is my first time handling permissions in Drupal 8+, so I may have missed something.

+++ b/modules/contact_permissions/contact_permissions.module
@@ -0,0 +1,61 @@
+ $entity_types['contact_form']->setAccessClass('\Drupal\contact_permissions\ContactFormAccessControlHandler');
we also need to switch the permissions route provider from EntityPermissionsRouteProviderWithCheck to EntityPermissionsRouteProvide

I'm actually not following this one -- I don't see where EntityPermissionsRouteProviderWithCheck is used. Nor have I been able to figure out the intent of the note with a little research. Happy to make changes if someone could clarify, though.