1

I have some nodes which I want to hide from anonymous users when they access via a direct URL e.g. /node/1275.

However if someone accesses this via the URL alias which is an md5 hash I want to allow anonymous users to access it. This is so that the node is essentially available only to people I give the link to.

How would I achieve this? Ideally I'd like to avoid installing more modules.


EDIT:

Here's the complete solution that I used in my custom module. As well as the access check it contains a new filter type that generates a md5 for each node, which you can use in the URL alias patterns page.

 /**
 * Implements hook_menu_alter
 */
function newsletter_access_menu_alter(&$items){
  $items['node/%node']['access callback'] = 'newsletter_access_check_node_access';
}

/**
 * Custom menu access callback for node/%node
 */
function newsletter_access_check_node_access($op, $node){

  // Note $op and $node are passed in default node_menu access arguments
  if ($op == 'view' && isset($node->nid)) {
     if($node->type == "newsletter") {
            // Allow access to anon if request_uri contains specific hash
            if (!user_is_logged_in()) {
              $hash = md5(drupal_get_private_key() . $node->nid);
              // request_uri includes alias
              $request = explode('/', request_uri());
              if (in_array($hash, $request)) {
                return TRUE;
              } else {
                return FALSE;
              }
            }
        }
      }
  // Fallback to allow regular node_access checks
  return node_access($op, $node);
}


function newsletter_access_token_info() {
  $types['my_tokens'] = array(
    'name' => t("My Tokens"),
    'description' => t("My Token Category"),
  );

  $tokens['my_md5'] = array(
    'name' => t("Unique MD5 Hash"),
    'description' => t("The md5 hash unique for each node."),
  );

  return array(
    'types' => $types,
    'tokens' => array(
      'my_tokens' => $tokens,
    ),
  );

}

function newsletter_access_tokens($type, $tokens, array $data = array(), array $options = array()) {

  $replacements = array();

  if ($type == 'my_tokens') {
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'my_md5':
          $replacements[$original] =  md5(drupal_get_private_key() . $data['node']->nid);
        break;
      }
    }
  }

  return $replacements;
}

?>
2
  • looks good, the only thing I wonder about is making the hash a bit more secure. I added that MD5 one as a temporary example, but it would perhaps be better to use an additional salt paramter and/or perhaps a function like hash_hmac instead, otherwise fine. Jun 24, 2013 at 23:20
  • Wrapping the $node->nid in an additional MD5 could suffice. Jun 24, 2013 at 23:54

2 Answers 2

4

You could do something like

/**
 * Implements hook_menu_alter
 */
function MY_MODULE_menu_alter(&$items){
  $items['node/%node']['access callback'] = 'MY_MODULE_check_node_access';
}

/**
 * Custom menu access callback for node/%node
 */
function MY_MODULE_check_node_access($op, $node){
  // Note $op and $node are passed in default node_menu access arguments
  if ($op == 'view' && isset($node->nid)) {
    // Allow access to anon if request_uri contains specific hash
    if (!user_is_logged_in()) {
      $hash = md5(drupal_get_private_key() . md5($node->nid));
      // request_uri includes alias
      $request = explode('/', request_uri());
      if (in_array($hash, $request)) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
  }
  // Fallback to allow regular node_access checks
  return node_access($op, $node);
}
7
  • 1
    that's a really flexible solution, and i could probably use that for a number of other permissions issues! i didn't know how to use access callbacks so it's good to know how to use it.
    – ankles
    Jun 22, 2013 at 9:53
  • 1
    You're welcome, note I updated to add a condition for authenticated users to fallback to regular node_access. When you add a custom access callback, it's often a good idea to fallback to the original access callback if your conditions pass. Jun 22, 2013 at 10:07
  • 1
    I think $items needs to be &$items to work (for other people's benefit this means the value get passed back)
    – ankles
    Jun 23, 2013 at 22:27
  • @AlexLee thanks, I think you're right. It was written on the fly. Code adjusted. Jun 23, 2013 at 22:48
  • hi if some one drupal_http_request /node/1275 will he able to retrieve node data?
    – Ali Nouman
    Jun 24, 2013 at 7:43
0

Have you tried in the template.php? what if you do something like:

function yourtheme_preprocess_page(&$variables, $hook){
   if((arg(0) == 'node') && (!$user->uid)){
      //redirect
   }
}

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.