We’ve been trying to get this to work for quite some time now, but so far no progress. As soon as we enable a node access control module (Content Access, Node Access, Simple Access), where we limit Anonymous access to several nodes, we can’t request www.example.com/node.json anymore as anonymous. The result is a 403 header, where we would expect to get the list of nodes without the ones we are not allowed to see.

I’ve traced this back to the restws_entity_node_access function, where, if no specific node is requested this code is ran.

  // No node is provided. Check for access to all nodes.
  if (user_access('bypass node access', $account)) {
    return TRUE;
  }
  if (!user_access('access content', $account)) {
    return FALSE;
  }
  if ($op == 'view' && node_access_view_all_nodes($account)) {
    return TRUE;
  }

In particular the node_access_view_all_nodes function causes this as it says:

Checks to see whether any module grants global 'view' access to a user account; global 'view' access is encoded in the {node_access} table as a grant with nid=0. If no node access modules are enabled, node.module defines such a global 'view' access grant. https://api.drupal.org/api/drupal/modules!node!node.module/function/node...

Since there is a content access module, this functions returns FALSE and restws returns a 403.

While further investigating that function I've found that the phpDoc comments above the function say:

@todo Remove this once https://drupal.org/node/1780646 is fixed.

Which has been fixed in the latest dev release of Entity API. So I was wondering if there was anything in the works to remove this function and / or implement a check for individual node access callbacks?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dolcaer’s picture

I've been thinking of this node_access_view_all_nodes part, and wonder if it is really necessary. According to the documentation on the node_access_view_all_nodes function page:

Checks to see whether any module grants global 'view' access to a user account; global 'view' access is encoded in the {node_access} table as a grant with nid=0. If no node access modules are enabled, node.module defines such a global 'view' access grant.

So if no node access modules are enabled, the node module provides a global 'view' grant and returns TRUE. If there however IS a node access modules enabled, that module would be responsible for the filtering of restricted nodes. Thus users should still not be able to see nodes they don't have access to.

We've just tried the following code (with content access, I don't know about the others), and it works as expected. Returning all nodes the anonymous user has access to, and stripping the ones he doesn't.

  // No node is provided. Check for access to all nodes.
  if (user_access('bypass node access', $account)) {
    return TRUE;
  }
  if (!user_access('access content', $account)) {
    return FALSE;
  }
  if ($op == 'view') { // DISABLED THIS: && node_access_view_all_nodes($account)) {
    return TRUE;
  }

This should always work, because it is open in the case there is no content access module (node module defines the global 'view') it would have returned TRUE anyway. If there is a content access module, it is up to the module to handle the filtering.

timlie’s picture

Can confirm that this solved the issue.
Nodes are shown in the listing respecting the node access permissions they have.

Neograph734’s picture

Title: Access denied when requesting a list of nodes of which some are not viewable » Access denied when requesting a list of nodes with node/entity-access modules enabled
Category: Support request » Bug report
Status: Active » Needs review
FileSize
479 bytes

I agree with above comments and have added a patch.

Grayside’s picture

Status: Needs review » Closed (duplicate)
Related issues: +#2237879: Remove custom restws_entity_node_access() hack
Neograph734’s picture

Status: Closed (duplicate) » Active

As of now this is still an issue. The custom hack is gone but the code now checks the access using the entity API's entity_metadata_no_hook_node_access access callback. Which in turn (correctly) returns false when the access to ALL nodes is requested.

In certain use cases where a site has internal and external content where the external content should be accessible by anonymous users this will always fail as these users will never have access to all nodes or permissions to bypass node access. Making RestWS a useless module in these cases.

In my opinion this module should attempt to gain access to all nodes and when it fails, it could perhaps attempt to get view access to some nodes. I've altered the restws_handle_request function to do this below.

function restws_handle_request($op, $format, $resource_name, $id = NULL, $payload = NULL) {
  $message = $status_message = '';
  if ($resource = restws_resource_controller($resource_name)) {
    // Allow other modules to change the web service request or react upon it.
    $request = array(
      'op' => &$op,
      'format' => &$format,
      'resource' => &$resource,
      'id' => &$id,
      'payload' => &$payload,
    );
    drupal_alter('restws_request', $request);

    // Since there is no access callback for query we need to use view.
    $access_op = $op == 'query' ? 'view' : $op;

    if (user_access('access resource ' . $resource_name) && $resource->access($access_op, $id)) {
      try {
        $method = $op . 'Resource';
        if ($op == 'create') {
          print $format->$method($resource, $payload);
          $status_message = '201 Created';
        }
        elseif ($op == 'query') {
          if (!$resource instanceof RestWSQueryResourceControllerInterface) {
            throw new RestWSException('Querying not available for this resource', 501);
          }
          print $format->$method($resource, $payload);
        }
        else {
          print $format->$method($resource, $id, $payload);
        }
        drupal_add_http_header('Content-Type', $format->mimeType());
      }
      catch (RestWSException $e) {
        $message = check_plain($e->getHTTPError()) . ': ' . check_plain($e->getMessage());
        $status_message = $e->getHTTPError();
      }
    }
//
//
//
// Anonymous node read access begin
    elseif (user_access('access resource ' . $resource_name) && $op == 'query') {
      try {
        $method = $op . 'Resource';
        if (!$resource instanceof RestWSQueryResourceControllerInterface) {
          throw new RestWSException('Querying not available for this resource', 501);
        }
        print $format->$method($resource, $payload);
      }
      catch (RestWSException $e) {
        $message = check_plain($e->getHTTPError()) . ': ' . check_plain($e->getMessage());
        $status_message = $e->getHTTPError();
      }
    }
// Anonymous node read access end    
//
//
//
    else {
      $status_message = $message = '403 Forbidden';
      watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
    }
  }
  else {
    $status_message = $message = '404 Not Found';
  }

  restws_terminate_request($status_message, $message);
}
pmackay’s picture

I'm experiencing this issue as well. The above change enables downloading lists of nodes. Would it make sense to roll it into a patch?

mollux’s picture

FileSize
709 bytes

I made a patch of the above code to allow querying of entities by anonymous users.

aschmoe’s picture

Here is #5 in a patch.

aschmoe’s picture

Status: Active » Needs review

Moving to needs review for testing.

The last submitted patch, 7: restws-anonymous-query-access-2169363-7.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: restws-fixing-anon-node-access-2169363-8.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 8: restws-fixing-anon-node-access-2169363-8.patch, failed testing.

aschmoe’s picture

Not sure why the patch in #8 is failing... everything applies cleanly for me. Someone want to give it another shot?

Media Crumb’s picture

I found one bug with this patch. If im using any filters the url request gets redirected to node.json without the filters attached. For instance my filters are set as:

http://www.site.com/node?full=0&limit=10&type=blog

but anonymous users actually get:

http://www.site.com/node

Media Crumb’s picture

More info on the bug.

It seems to only happen for people using the none json url structure. For instance

http://www.site.com/node?full=0&limit=10&type=blog BREAKS

http://www.site.com/node.json?full=0&limit=10&type=blog WORKS

This is a problem for people who are using http://www.site.com/node/ID however so we would need a way to fix that before considering it a working patch.

Derek Devnich’s picture

I've tested both of the patches above, and they seem to succeed or fail in similar situations. For example, we have a "People" content type that we use for directory entries. The following anonymous request succeeds:
mysite.com/node.json?type=people&limit=10
mysite.com/node.xml?type=people&limit=10

Sorting by the built-in node title also works:
mysite.com/node.json?type=people&limit=10&sort=title
mysite.com/node.xml?type=people&limit=10&sort=title

However, attempting to sort by a user-defined database field fails:
mysite.com/node.json?type=people&limit=10&sort=field_people_last_name
mysite.com/node.xml?type=people&limit=10&sort=field_people_last_name

...even though these work fine for an authenticated user. The PDOException is:
PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'field_data_field_people_last_name0.nid' in 'where clause' in /var/www/drupal7-prod/htdocs/includes/database/database.inc on line 2171

I don't know enough about how restws handles permissions to know what's going on here. If it's helpful, the permissions-modifying module we're using on this site is the "View Unpublished" module.

cloudbull’s picture

having same thing here,

access denied even content type and restws permission set.

test url http://test1-bangkok.coconuts.co/node.json?type=event&field_price=420.13

any help is appreciated.

thanks
Keith

djouuuuh’s picture

I'm having the same issue.

I have Content Access enabled and all the users that are not with "bypass content access control" permission cannot request the data.

I'm very interested in the solution.