Jump to content
thetuningspoon

Best approach to handling page view permissions in an app built on ProcessWire

Recommended Posts

I've been getting more and more into building full fledged web apps using PW as a framework. I use PW for data modeling and storage, user management, etc., and extend the Page class for different templates to add functionality to specific types of pages/data models. It is a very simple and powerful way to develop. 

However, one thing that I have struggled with is finding the right way to approach page view access for users of an application (This would also apply to a password-protected area of any PW site). I'm going to try and boil this down to the most simple, common scenario, and go from there:

 

I am building an app where every page in the app (except for the login screen) should be password protected. Should I...

 

1. Turn off page view access in the template access settings for the guest user and use the settings to redirect the user to a login page.

This has the drawback that you cannot disable guest view of the home page (a built-in PW limitation that seems a bit arbitrary). You are also limited in how you can define what to do when the page is not viewable (you must use the options provided in the admin interface), and you do not have the option of continuing to load the page with an alternate view (for example, a login form). Also, sometimes it requires configuring a lot of settings for a lot of different templates.  It also doesn't give you page-specific access control.

 

2. Leave the access settings wide open but write some code at the top of my template files, init.php, or ready.php to redirect users who are not logged in.

This has the disadvantage that it only applies if ProcessWire gets that far into the page load process, and it doesn't effect any other aspect of ProcessWire (for example, whether the page is available in a $pages->find()). If I wanted, I could allow anyone to reach any page and just show/hide the content based on the user's permissions or role. If the user doesn't have permission, I could keep them on the same page but show the login. Once they logged in, they'd be on the page they were originally looking for.

 

3. Write my own hook before or after Page::viewable and/or ProcessPageView::execute (or somewhere else?) to switch access on or off and redirect based on my own requirements.

This should be more reliable and secure than #2 and more flexible than #1, but it feels kind of like reinventing the wheel. Maybe the best approach is some combination of #1 and #3, with #2 reserved only for showing and hiding individual sections of a page that is already viewable.

 

I'd be very interested to hear how others are handling this.

Share this post


Link to post
Share on other sites

This topic reminded my on my tests with reference based access control. I don't know about performance / production usability... But maybe You would take a look.

https://bitbucket.org/pwFoo/accessbyreference/src/23e6a3597028da4021e3e348e4222a3ad77685a9/AccessByReference/AccessByReference.module?at=master&fileviewer=file-view-default

Manage view / edit permissions based on a reference chain page -> group -> user.

Wow... it's three years old... So it's untested with PW versions newer than three years :rolleyes:

  • Like 1

Share this post


Link to post
Share on other sites

Thanks, pwFoo, I will check that out.

Here's another problem with method #1: The redirect URL field in the template Access settings is not relative to the subfolder that you have PW installed in (if you have it installed in a subfolder). I always develop in a subfolder, making this feature impractical.

Share this post


Link to post
Share on other sites

Hi, just a hint:

I do not yet have much experience in this but for a similar (but not exactly same) use case I use @adrian's PageProtector module which supports locking down all Unpublished and/or Hidden pages just by clicking two checkboxes. I use it to protect areas of the website which are not yet ready to be seen by the public.
Of course in my case Published pages should be accessible by guests, but you might want to take a look at the module's code to see how he did it so that you can extend it to Published pages as well:

 

  • Like 2

Share this post


Link to post
Share on other sites

An update:

I missed something in my last post. It is possible to use a page ID instead of a URL for the redirect in the template Access tab, which solves the subfolder issue.

However! For some strange reason, ProcessPageView::checkAccess specifically disallows using the home page (ID of 1) for the page ID here, silently overwriting it with page ID of the admin login form. This is unfortunate and ironic, as the enforced requirement of making the home page viewable to guests makes it a logical choice to serve as the login page for my application!

The other strange behavior here is that if you do use a page ID instead of a URL, ProcessWire does not actually do a $session->redirect() to the page at all, but just renders the content of the alternate page at the original requested URL. This seems to be how ProcessWire's admin login works as well. Perhaps a way to keep the user on the original requested page after they login? This was a surprise to me, as it's not mentioned in the UI.

I also found it interesting to discover that ProcessWire's own admin application shuns its own permissions system (or at least the template-level access part of it). If you look at the access settings for the Admin template, no view permissions are granted at all to any of the roles. Instead they are granted, I believe, based on the presence or absence of a single permission with the name of the process module associated with that page. This leads me to believe that I would be better off doing something similar in my own application.

 

The more I dive into this more confused I'm becoming. This is really the only part of ProcessWire that seems unintuitive to me. I get the permissions -> roles -> users bit, but the template level access permissions seem arbitrary to me. For example:

 

1. Why are only certain permissions available to set on a per-template basis?

2. Why are four of these permissions displayed prominently in a table while the rest are in the "additional edit permissions and overrides" section? 

3. What is it exactly that determines which permissions are special, and how do I create my own special permissions like these?

4. Why does the page-edit permission have 'sub-permissions' underneath it, but no other permission does?  Is there a way of structuring my own custom permissions in the same fashion?

Share this post


Link to post
Share on other sites

This post by Ryan partially answers your questions:

Regarding the admin login process, I do not know if it helps you or not but recently I used two hooks to use the admin login for frontend login as well:

<?php
$site_customers_pid = 'customers'
/* Redirects frontend user to /customers */
$wire->addHookAfter('Page::render', 'siteProtectPages', array('priority' => 654));
function siteProtectPages($event) {
	$current_page = $event->object;
	if ($current_page->template == 'admin') return; // ignore admin pages including admin login page
	if ($current_page->path() == "/{$site_customers_pid}/") {
		wire('session')->removeNotices(); // needed to clear "left over" notifications of the login page
	}
}

/* logout hooks for non admin users
 * see: https://processwire.com/talk/topic/15911-redirect-home-after-logout/#comment-142170
 */
$wire->addHookAfter("Session::logoutSuccess", null, "setRedirect");
function setRedirect(HookEvent $event) {
	if (wire('user')->hasRole('customer')) {
		wire('session')->redirect("/{$site_customers_pid}/");
	}
}

And "What to do when user attempts to view a page and has no access?" is set to: "/admin/login/". With this setup when the visitor goes to /customers/, they see the standard login form. After logging in, they get access to /customers/. I styled the admin header and login form with CSS and JS to make it more like the site's design.

I know that this is somewhat off topic but it might give you and idea or two.

Share this post


Link to post
Share on other sites

Here is a simple hook I put together to allow for a custom redirect when a page is not viewable:

/**
 * Automatically redirects users to the login page if they do not have view access to a page. To use, set "Show a 404 Page" in your template's Access settings.
 */
wire()->addHookBefore('ProcessPageView::pageNotFound', function($e) {
	$page = $e->arguments[0];
	if($page === null || $page instanceof NullPage) return; // This was a real 404, not caused by page access (have to check for NullPage, not just null as implied in ProcessPageView docs)

	$e->session->redirect($this->pages->get(1)->url, false); // Redirect to the home page (login)
});

This solves my immediate need. I can then hook Page::viewable() as needed and let PW take care of the rest.

I'm hoping @ryan may have some guidance on all of this.

  • Like 1

Share this post


Link to post
Share on other sites
46 minutes ago, szabesz said:

This post by Ryan partially answers your questions:

 

 

Thanks for that. I've read it over a few times now but I'm not quite getting it.

Quote

Having page-view, page-edit, page-create, page-add in the template access is actually unnecessary for us, but it does reduce the quantity of permissions and roles necessary to achieve a particular access control scenario 

How is this unnecessary? How else would one determine where each permission is applicable? Does he mean you could create a separate permission for each template, e.g. page-edit-home page-edit-basic-page, etc.?

 

Quote

I'd rather go in the opposite direction and reduce what permissions can be assigned at the template (by default), but make it definable. Imagine going to Modules > Process > ProcessTemplate > [edit module settings] and selecting from an asmSelect which permissions should be assignable at the template level -- a nice power user option?

Yes, yes, yes... THIS sounds like the type of solution I would expect from ProcessWire. Simple, powerful, and un-opinionated. What became of this?

  • Like 2

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By lenoir
      Is it possible to let people edit a page without having to have a user-role?
      My case is the following:
      Visitors fill in a form (Formbuilder) which is saved to pages. They get a confirmation email which could contain a unique editing link. In case they need to update some information, they can click on this link, edit the fields and save. 
      Am I totally off? Is there a better practice? 
    • By DV-JF
      Hi, 
      I'm using this kind of setup (https://processwire.com/blog/posts/language-access-control-and-more-special-permissions/#language-page-edit-permissions) in order to control the page edit permissions. Now I'm wondering if it's possible to hide the "none-ediable" language-tabs instead of striking them through.

      Many greets...
       
    • By Kiwi Chris
      I have a role that has page edit, view, and clone permissions on a specific template.
      If a page using the template is locked by a user in a role with lock/unlock permissions on the template , the only button alongside it in the page tree is view, for users who don't have lock/unlock permissions.
      If however, I also give the role page-lock permission on the template, they then get additional buttons, edit, copy, and unlock.
      I don't actually want to give this role unlock permissions, but I do want the copy (clone) button to display alongside the page in the page tree.
      Elsewhere, I've discussed how I've worked out how to create a hook to unlock the copy, but I want to keep the original page so a user without lock permissions can't unlock from the page tree it to make changes.
      Question: What method should I hook into to intercept any attempt to change the lock status?
    • By pwFoo
      Hi,
      I try to add page-edit-own and page-delete-own permissions, but it's strange...
      If a add the custom permissions it looks like both are children of page-edit respectively page-delete. I played with added / revoked permissions, but I can't get it work, that a user of a role just can delete own content.
      First the user can't delete any content and now the user can delete own and foreign pages 🤪
      Is there a tutorial to learn more about the PW permissions?
      Or do I have to rename the permissions to page-own-edit and page-own-delete to be independent from page-edit and page-delete?
×
×
  • Create New...