I'd like to provide a custom "access-denied" node, preferably with a login form embedded in it. When the user submits the form, they should be redirected to the page they were trying to access intially.

By default, when using the build-in access-denied function, and the user then logs in using the normal login-block, it works as it should, with the user being directed to the right page.

However, when I implement a custom access-denied page, and a user logs in from there, they are returned to the "access-denied" page as though it were the node they were trying to access.

How can I preserve the original node request to get the proper redirect?

Thanks!

Comments

coreyp_1’s picture

How to solve your problem depends on how you are serving your custom Access Denied page.

Ideally, you sould be able to use Drupal's built-in fuctions to redirect the user back to the page they originally requested. In your login form (you are using Drupal's form() function, right?), just use url('user/login', drupal_get_destination()) for the form target.

Hope this helps.

stmind’s picture

Thanks for the response.

When used on a custom access-denied node, drupal_get_destination() returns the path to the current node, not the one that was "requested but denied".

I've also tried just getting $_GET['q'], but that also just prints the current node.

To be clearer, my custom access-denied node is node 26, with an alias of 'private'. I have a custom 403 set to "node/26".

This node is a drupal page, with a text greeting and a php block containing

<p>Access denied.</p>

<?php
print "<p>get_destination is: ".drupal_get_destination()."</p>";
print "<p>q is: ". $_GET['q'] ."</p>";
print "<p>url(q) is:  ".url($_GET['q']) ."</p>";
?>

I have another node, let's say node 10, that only accessible to authenticated users.

When an anonymous user attempts to access node/10, the browser's location bar is /node/10 but they are given node 26, with the following output:

Access denied.

get_destination is destination=node%2F26

q is node/26

url(q) is: private

So how do I get the intended destination?

coreyp_1’s picture

Just a shot in the dark before I really start digging into this, but have you looked in the $_SERVER[] superglobal?

coreyp_1’s picture

OK, this can be done in the code, but I don't know how pretty it is.

the function drupal_access_denied() changes the node request that is returned by get_destination(), etc.

My response is to set a global variable with the information *you* want just before that change, so you can reference it later.

Around line 226 of common.inc add

  global $destbefore403;
  $destbefore403 = drupal_get_destination();

then, in your custom 403 page, use this for the target of your form:

global $destbefore403;
url('user/login', $destbefore403);

Let's see where that gets us...

stmind’s picture

That solution works for me. Here's my 2.6.3 common.inc drupal_access_denied() after your changes:

<?php
function drupal_access_denied() {
  header('HTTP/1.0 403 Forbidden');
  watchdog('access denied', t('%page denied access.', array('%page' => theme('placeholder', $_GET['q']))), WATCHDOG_WARNING, l(t('view'), $_GET['q']));

  // Hack: STM - Added the following global to allow retrieval from a custom 403 page.
  global $destbefore403;
  $destbefore403 = drupal_get_destination();
  // End hack

  $path = drupal_get_normal_path(variable_get('site_403', ''));
  $status = MENU_NOT_FOUND;
  if ($path) {
    menu_set_active_item($path);
    $status = menu_execute_active_handler();
  }

  if ($status != MENU_FOUND) {
    drupal_set_title(t('Access denied'));
    print theme('page', message_access());
  }
}
?>

I'm not too thrilled about modifying Drupal code, as of yet I have avoided it (which speaks very highly of the theming and module APIs). But, this is a small hack that I can live with, and I've documented it in my site's changelog so I'm not scratching my head later at upgrade time. It would be nice if in the future there could be a better request object available that contained this type of information.

So my custom error 403 node looks like this:

<p>Whoops!</p>

<p>We're sorry, the page you were trying to access is not available to you.</p>

<?php
global $user;
global $destbefore403;

 if ($user->uid) {
   print "<p>You are currently logged in as <b>". $user->name ."</b>.";
   print "<p>If you do not have access to a page it may be because of a configuration problem, so if you have any questions, please contact the site administrator.</p>";
 } elseif (!$user->uid) {
   print "<p>This is probably because you are not logged in.</p><p>To access this page, please log in:</p>";

  $output .= form_textfield(t('Username'), 'name', $edit['name'], 15, 64);
  $output .= form_password(t('Password'), 'pass', $pass, 15, 64);
  $output .= form_submit(t('Log in'));
  $output .= "</div>";
  $output = form($output, 'post', url('user/login', $destbefore403));
  print $output;
}
?>

I have this node aliased to "/private". It is also flagged as unpublished, so it does not show up in searches and lists. Lastly, I configured the login block not to appear on the page "private".

In my site settings I have specified the custom error 403 page as "private".

Works like a charm. Thanks for your help!

behindthepage’s picture

Put the following form in a page and define it as your access-denied page. You can set the destination in the FORM tag. I have left all the < tags off the beginning of the lines as I was having trouble posting it.

FORM METHOD=POST ACTION="?q=user/login&amp;destination=node/6">
Please enter your User Name
input type="text" name="edit[name]" size="25" maxlength="15"><br>
Please enter your password
input type="password" name="edit[pass]" size="25" maxlength="10"><br>
input type="submit" class="form-submit" name="op" value="Log in"  />
/form>
a href="?q=user/password" title="Request new password via e-mail.">Request new password</a>

gpdinoz
Thought for the day
"If you're not getting the answers you want maybe you're asking the wrong questions"

Regards
Geoff

stmind’s picture

Thanks for your reponse. The problem is that the destination is not known ahead of time, it could be any node that they were denied access to.

stupiddingo’s picture

I was able to determine the original requested URL on my custom 403 page by accessing Server Variables.

<?php
function PageURI() {
 $pageURI = "http";
 if ($_SERVER["HTTPS"] == "on") {$pageURI .= "s";}
$pageURI .= "://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
 return $pageURI;
}
$pageurl = urlencode(PageURI()."?cachebuster=".rand());
?>

You can then add $pageurl to the destination parameter in the querystring to provide links for round-trip login that returns to the original request. I've also used this URI to build a simple request access form for users that are authenticated but not in the correct role/organic group to access the URI.

e.g.

<?php global $user; ?>
<?php if ($user->uid) { ?> 
  <p>Sorry <?php print $user->name; ?>, you don't have permission to view the page you've just tried to access.</p>
 <p>If this doesn't sound right, we're very sorry.  Please provide any details that will assist the us in determining who you are and why you should have access to this page.</p>
  <form action="requestaccess?destination=<?php print $pageurl; ?>" method="post" id="requestform">
        <div>
            <fieldset><legend>Request Access</legend>
                <div>
                    <label for="mailFromDisplay">Your Username</label>
                    <input id="mailFromDisplay" name="mailFromDisplay" readonly="readonly" type="text" value="<?php print $user->name; ?>" /> 
                </div>
                <div>
                    <label for="returnUrl">Page Requested</label>
                    <input id="returnUrl" name="returnUrl" readonly="readonly" type="text" value="<?php print urldecode($pageurl); ?>" /> 
                </div>
                <div>
                    <label for="mailBody">Message</label><br/>
                    <textarea class="ifwis-textarea" cols="40" id="mailBody" multiline="true" name="mailBody" rows="2">
</textarea>                
                    <br/><input type="submit" value="Request Access" />
                </div>
            </fieldset>
        </div>
    </form>
<?php } else { ?>
    <p>Only registered users in specific groups may access the page you requested.</p>   
    <h3>Logging in may make this all go away.</h3>
    <ul>
        <li><a title="Log In" href="user/login?destination=<?php print $pageurl; ?>">Login</a> 
	to see if you can access this page once authenticated.</li> 
        <li><a title="Create an Account" href="user/register?destination=<?php print $pageurl; ?>">Register</a> if you don't yet have an account.</li> 
	</ul>
    </div>
<?php } ?>

I'm not certain if this works in all configurations, but it's working well for me on a production site.

atolson’s picture

This is exactly what I'm looking for (minus the registration form). Unfortunately, I can't seem to get it to work. (Drupal 6.20)

I have created an Access Denied node with the following PHP input:

<?php
function PageURI() {
 $pageURI = "http";
 if ($_SERVER["HTTPS"] == "on") {$pageURI .= "s";}
$pageURI .= "://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
 return $pageURI;
}
$pageurl = urlencode(PageURI()."?cachebuster=".rand());
?>

<?php global $user; ?>
<p>Sorry <?php print $user->name; ?>, you don&#39;t have permission to view the page you&#39;ve just tried to access.</p>
<p>If this doesn&#39;t sound right, we&#39;re very sorry. Please provide any details that will assist the us in determining who you are and why you should have access to this page.</p>
<p>Only registered users in specific groups may access the page you requested.</p>
<h3>Logging in may make this all go away.</h3>
<ul>
<li><a href="user/login?destination=<?php print $pageurl; ?>" title="Log In">Login</a> to see if you can access this page once authenticated.</li>
<li><a href="user/register?destination=<?php print $pageurl; ?>" title="Create an Account">Register</a> if you don&#39;t yet have an account.</li>
</ul>

And I get the following error:

Fatal error: Cannot redeclare pageuri() (previously declared in /var/www/drupal/includes/common.inc(1696) : eval()'d code:3) in /var/www/drupal/includes/common.inc(1696) : eval()'d code on line 7

Have I misunderstood where the PageURI function is supposed to go?

Thanks in advance for any help that can be thrown my way.

atolson’s picture

I've discovered the r4032login module (a.k.a. Redirect 403 to User Login) and am using that to accomplish the flow-through on access denied pages.

stupiddingo’s picture

To be clear, in my example above I was using external webserver authentication, not drupal. If you are logging via drupal http://drupal.org/project/r4032login is a far more elegant solution.