Jump to content

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


thetuningspoon
 Share

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.

Link to comment
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
Link to comment
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.

Link to comment
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
  • Thanks 1
Link to comment
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?

Link to comment
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.

Link to comment
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
Link to comment
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
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...