diff --git a/src/Datasource/DatasourceInterface.php b/src/Datasource/DatasourceInterface.php
index 1fdc5df..1361b9e 100644
--- a/src/Datasource/DatasourceInterface.php
+++ b/src/Datasource/DatasourceInterface.php
@@ -30,6 +30,16 @@ use Drupal\search_api\Plugin\IndexPluginInterface;
 interface DatasourceInterface extends IndexPluginInterface {
 
   /**
+   * Retrieves the entity type ID of items in this datasource.
+   *
+   * @return string|null
+   *   If this datasource contains entities (of a single type), their entity
+   *   type ID. NULL otherwise.
+   *
+   */
+  public function getEntityTypeId();
+
+  /**
    * Retrieves the properties exposed by the underlying complex data type.
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
diff --git a/src/Datasource/DatasourcePluginBase.php b/src/Datasource/DatasourcePluginBase.php
index 633cdc1..f30ad14 100644
--- a/src/Datasource/DatasourcePluginBase.php
+++ b/src/Datasource/DatasourcePluginBase.php
@@ -45,6 +45,14 @@ abstract class DatasourcePluginBase extends IndexPluginBase implements Datasourc
   /**
    * {@inheritdoc}
    */
+  public function getEntityTypeId() {
+    $plugin_definition = $this->getPluginDefinition();
+    return isset($plugin_definition['entity_type']) ? $plugin_definition['entity_type'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getViewModes() {
     return array();
   }
diff --git a/src/Plugin/SearchApi/Datasource/ContentEntityDatasource.php b/src/Plugin/SearchApi/Datasource/ContentEntityDatasource.php
index 24c34b9..caf69fe 100644
--- a/src/Plugin/SearchApi/Datasource/ContentEntityDatasource.php
+++ b/src/Plugin/SearchApi/Datasource/ContentEntityDatasource.php
@@ -315,19 +315,6 @@ class ContentEntityDatasource extends DatasourcePluginBase implements ContainerF
   }
 
   /**
-   * Returns the entity type id.
-   *
-   * @return string
-   *   The entity type id.
-   */
-  public function getEntityTypeId() {
-    // Get the plugin definition.
-    $plugin_definition = $this->getPluginDefinition();
-    // Get the entity type.
-    return $plugin_definition['entity_type'];
-  }
-
-  /**
    * Returns the entity type definition.
    *
    * @return \Drupal\Core\Entity\EntityTypeInterface
diff --git a/src/Plugin/SearchApi/Processor/CommentAccess.php b/src/Plugin/SearchApi/Processor/CommentAccess.php
index fe414e7..0c2ac53 100644
--- a/src/Plugin/SearchApi/Processor/CommentAccess.php
+++ b/src/Plugin/SearchApi/Processor/CommentAccess.php
@@ -17,16 +17,7 @@ class CommentAccess extends NodeAccess {
   /**
    * {@inheritdoc}
    */
-  public static function supportsIndex(IndexInterface $index) {
-    // @todo Re-introduce Datasource::getEntityType() for this?
-    foreach ($index->getDatasources() as $datasource) {
-      $definition = $datasource->getPluginDefinition();
-      if (isset($definition['entity_type']) && $definition['entity_type'] === 'comment') {
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
+  protected static $entityTypeId = 'comment';
 
   /**
    * Overrides \Drupal\search_api\Plugin\SearchApi\Processor\NodeAccess::getNode().
@@ -53,4 +44,5 @@ class CommentAccess extends NodeAccess {
 
     parent::submitConfigurationForm($form, $form_state);
   }
+
 }
diff --git a/src/Plugin/SearchApi/Processor/NodeAccess.php b/src/Plugin/SearchApi/Processor/NodeAccess.php
index 5220125..5b69aff 100644
--- a/src/Plugin/SearchApi/Processor/NodeAccess.php
+++ b/src/Plugin/SearchApi/Processor/NodeAccess.php
@@ -6,8 +6,10 @@
 
 namespace Drupal\search_api\Plugin\SearchApi\Processor;
 
+use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\Core\TypedData\DataDefinition;
 use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\Exception\SearchApiException;
 use Drupal\search_api\Index\IndexInterface;
 use Drupal\search_api\Processor\ProcessorPluginBase;
 use Drupal\search_api\Query\QueryInterface;
@@ -22,13 +24,18 @@ use Drupal\search_api\Query\QueryInterface;
 class NodeAccess extends ProcessorPluginBase {
 
   /**
+   * Defines the entity type id that this processor can handle.
+   *
+   * @var string
+   */
+  protected static $entityTypeId = 'node';
+
+  /**
    * {@inheritdoc}
    */
   public static function supportsIndex(IndexInterface $index) {
-    // @todo Re-introduce Datasource::getEntityType() for this?
     foreach ($index->getDatasources() as $datasource) {
-      $definition = $datasource->getPluginDefinition();
-      if (isset($definition['entity_type']) && $definition['entity_type'] === 'node') {
+      if ($datasource->getEntityTypeId() == static::$entityTypeId) {
         return TRUE;
       }
     }
@@ -40,10 +47,9 @@ class NodeAccess extends ProcessorPluginBase {
    */
   public function alterPropertyDefinitions(array &$properties, DatasourceInterface $datasource = NULL) {
     if (!$datasource) {
-      return NULL;
+      return;
     }
-    $datasource_definition = $datasource->getPluginDefinition();
-    if (isset($datasource_definition['entity_type']) && $datasource_definition['entity_type'] === 'node') {
+    if ($datasource->getEntityTypeId() === static::$entityTypeId) {
       $definition = array(
         'label' => t('Node access information'),
         'description' => t('Data needed to apply node access.'),
@@ -61,27 +67,31 @@ class NodeAccess extends ProcessorPluginBase {
 
     if (!isset($account)) {
       // Load the anonymous user.
-      $account = drupal_anonymous_user();
+      $account = new AnonymousUserSession();
     }
 
     foreach ($items as $id => $item) {
-      // @todo Only process nodes (check '#datasource' key).
+      if (!in_array($this->index->getDatasource($item['#datasource'])->getEntityTypeId(), array('node', 'comment'))) {
+        continue;
+      }
+
       /** @var $node \Drupal\Node\NodeInterface */
-      $node = $this->getNode($item);
-      // Check whether all users have access to the node.
+      $node = $this->getNode($item['#item']);
+      // If the view access is not granted by default to anonymous user check
+      // for grants.
       if (!$node->access('view', $account)) {
         // Get node access grants.
         $result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $node->id()));
 
         // Store all grants together with their realms in the item.
         foreach ($result as $grant) {
-          $items[$id]->search_api_access_node[] = "node_access_{$grant->realm}:{$grant->gid}";
+          $items[$id]['search_api_access_node'][] = "node_access_{$grant->realm}:{$grant->gid}";
         }
       }
       else {
         // Add the generic view grant if we are not using node access or the
         // node is viewable by anonymous users.
-        $items[$id]->search_api_access_node = array('node_access__all');
+        $items[$id]['search_api_access_node'] = array('node_access__all');
       }
     }
   }
@@ -130,7 +140,94 @@ class NodeAccess extends ProcessorPluginBase {
    * {@inheritdoc}
    */
   public function preprocessSearchQuery(QueryInterface $query) {
-    // @todo Implement.
+    $entity_type_id = $this->index->getDatasource(static::$entityTypeId)->getEntityTypeId();
+
+    if (!$query->getOption('search_api_bypass_access')) {
+      $account = $query->getOption('search_api_access_account', \Drupal::currentUser());
+      if (is_numeric($account)) {
+        $account = user_load($account);
+      }
+      if (is_object($account)) {
+        try {
+          $this->_search_api_query_add_node_access($account, $query, $entity_type_id);
+        }
+        catch (SearchApiException $e) {
+          watchdog_exception('search_api', $e);
+        }
+      }
+      else {
+        watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $query->getOption('search_api_access_account', \Drupal::currentUser())), WATCHDOG_WARNING);
+      }
+    }
+  }
+
+  /**
+   * Adds a node access filter to a search query, if applicable.
+   *
+   * @param object $account
+   *   The user object, who searches.
+   * @param SearchApiQueryInterface $query
+   *   The query to which a node access filter should be added, if applicable.
+   * @param string $type
+   *   (optional) The type of search – either "node" or "comment". Defaults to
+   *   "node".
+   *
+   * @throws SearchApiException
+   *   If not all necessary fields are indexed on the index.
+   */
+  protected function _search_api_query_add_node_access($account, QueryInterface $query, $type = 'node') {
+    // Don't do anything if the user can access all content.
+    if (user_access('bypass node access', $account)) {
+      return;
+    }
+
+    $is_comment = ($type == 'comment');
+
+    // Check whether the necessary fields are indexed.
+    $fields = $query->getIndex()->options['fields'];
+    $required = array('search_api_access_node', 'status');
+    if (!$is_comment) {
+      $required[] = 'author';
+    }
+    foreach ($required as $field) {
+      if (empty($fields[$field])) {
+        $vars['@field'] = $field;
+        $vars['@index'] = $query->getIndex()->name;
+        throw new SearchApiException(t('Required field @field not indexed on index @index. Could not perform access checks.', $vars));
+      }
+    }
+
+    // If the user cannot access content/comments at all, return no results.
+    if (!user_access('access content', $account) || ($is_comment && !user_access('access content', $account))) {
+      // Simple hack for returning no results.
+      $query->condition('status', 0);
+      $query->condition('status', 1);
+      watchdog('search_api', 'User @name tried to execute a search, but cannot access content.', array('@name' => theme('username', array('account' => $account))), WATCHDOG_NOTICE);
+      return;
+    }
+
+    // Filter by the "published" status.
+    $published = $is_comment ? COMMENT_PUBLISHED : NODE_PUBLISHED;
+    if (!$is_comment && user_access('view own unpublished content')) {
+      $filter = $query->createFilter('OR');
+      $filter->condition('status', $published);
+      $filter->condition('author', $account->uid);
+      $query->filter($filter);
+    }
+    else {
+      $query->condition('status', $published);
+    }
+
+    // Filter by node access grants.
+    $filter = $query->createFilter('OR');
+    $grants = node_access_grants('view', $account);
+    foreach ($grants as $realm => $gids) {
+      foreach ($gids as $gid) {
+        $filter->condition('search_api_access_node', "node_access_$realm:$gid");
+      }
+    }
+    $filter->condition('search_api_access_node', 'node_access__all');
+    $query->filter($filter);
   }
 
 }
diff --git a/src/Query/Query.php b/src/Query/Query.php
index a270538..be0d14e 100644
--- a/src/Query/Query.php
+++ b/src/Query/Query.php
@@ -87,28 +87,9 @@ class Query implements QueryInterface {
    * @param \Drupal\search_api\Index\IndexInterface $index
    *   The index the query should be executed on.
    * @param array $options
-   *   Associative array of options configuring this query. Recognized options
-   *   are:
-   *   - conjunction: The type of conjunction to use for this query - either
-   *     'AND' or 'OR'. 'AND' by default. This only influences the search keys,
-   *     filters will always use AND by default.
-   *   - 'parse mode': The mode with which to parse the $keys variable, if it
-   *     is set and not already an array. See DefaultQuery::parseModes() for
-   *     recognized parse modes.
-   *   - offset: The position of the first returned search results relative to
-   *     the whole result in the index.
-   *   - limit: The maximum number of search results to return. -1 means no
-   *     limit.
-   *   - 'filter class': Can be used to change the FilterInterface
-   *     implementation to use.
-   *   - 'search id': A string that will be used as the identifier when storing
-   *     this search in the Search API's static cache.
-   *   - search_api_access_account: The account which will be used for entity
-   *     access checks, if available and enabled for the index.
-   *   - search_api_bypass_access: If set to TRUE, entity access checks will be
-   *     skipped, even if enabled for the index.
-   *   All options are optional. Third-party modules might define and use other
-   *   options not listed here.
+   *   Associative array of options configuring this query. See
+   *   \Drupal\search_api\Query\QueryInterface::create() for a list of defined
+   *   ones.
    *
    * @throws SearchApiException
    *   If a search on that index (or with those options) won't be possible.
diff --git a/src/Query/QueryInterface.php b/src/Query/QueryInterface.php
index 067324f..b48f9fb 100644
--- a/src/Query/QueryInterface.php
+++ b/src/Query/QueryInterface.php
@@ -23,7 +23,27 @@ interface QueryInterface {
    * @param IndexInterface $index
    *   The index for which the query should be created.
    * @param array $options
-   *   (optional) The options to set for the query.
+   *   (optional) The options to set for the query. Recognized options are:
+   *   - conjunction: The type of conjunction to use for this query - either
+   *     'AND' or 'OR'. 'AND' by default. This only influences the search keys,
+   *     filters will always use AND by default.
+   *   - 'parse mode': The mode with which to parse the $keys variable, if it
+   *     is set and not already an array. See DefaultQuery::parseModes() for
+   *     recognized parse modes.
+   *   - offset: The position of the first returned search results relative to
+   *     the whole result in the index.
+   *   - limit: The maximum number of search results to return. -1 means no
+   *     limit.
+   *   - 'filter class': Can be used to change the FilterInterface
+   *     implementation to use.
+   *   - 'search id': A string that will be used as the identifier when storing
+   *     this search in the Search API's static cache.
+   *   - search_api_access_account: The account which will be used for entity
+   *     access checks, if available and enabled for the index.
+   *   - search_api_bypass_access: If set to TRUE, entity access checks will be
+   *     skipped, even if enabled for the index.
+   *   All options are optional. Third-party modules might define and use other
+   *   options not listed here.
    *
    * @return static
    *   A query object to use.
diff --git a/src/Tests/SearchApiCommentAccessProcessorTest.php b/src/Tests/SearchApiCommentAccessProcessorTest.php
new file mode 100644
index 0000000..974ec59
--- /dev/null
+++ b/src/Tests/SearchApiCommentAccessProcessorTest.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search_api\Tests\SearchApiCommentAccessProcessorTest.
+ */
+
+namespace Drupal\search_api\Tests;
+
+use Drupal\search_api\Plugin\SearchApi\Processor\CommentAccess;
+use Drupal\system\Tests\Entity\EntityUnitTestBase;
+
+/**
+ * Tests the CommentAccess processor.
+ */
+class SearchApiCommentAccessProcessorTest extends EntityUnitTestBase {
+
+  /**
+   * Modules to enable for this test.
+   *
+   * @var array
+   */
+  public static $modules = array('user', 'node', 'search_api', 'comment', 'entity_reference');
+
+  /**
+   * The processor used for these tests.
+   *
+   * @var \Drupal\search_api\Processor\ProcessorInterface
+   */
+  protected $processor;
+
+  /**
+   * @var \Drupal\comment\Entity\Comment[]
+   */
+  protected $comments;
+
+  /**
+   * @var \Drupal\node\Entity\Node
+   */
+  protected $node;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests CommentAccess Processor Plugin',
+      'description' => 'Tests if node access processor lets through only nodes',
+      'group' => 'Search API',
+    );
+  }
+
+  /**
+   * Creates a new processor object for use in the tests.
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $name = $this->randomName();
+    $index = entity_create('search_api_index', array(
+      'machine_name' => strtolower($name),
+      'name' => $name,
+      'status' => TRUE,
+      'datasourcePluginIds' => array('entity:comment')
+    ));
+    $index->save();
+
+    /** @var \Drupal\search_api\Processor\ProcessorPluginManager $plugin_manager */
+    $plugin_manager = \Drupal::service('plugin.manager.search_api.processor');
+    $this->processor = $plugin_manager->createInstance('search_api_node_access_processor', array('index' => $index));
+
+    $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_revision', 'node_access'));
+    $this->installSchema('comment', array('comment'));
+
+    // Create a node type for testing.
+    $type = entity_create('node_type', array('type' => 'page', 'name' => 'page'));
+    $type->save();
+
+    // Create anonymous user name.
+    $role = entity_create('user_role', array(
+      'id' => 'anonymous',
+      'label' => 'anonymous',
+    ));
+    $role->save();
+
+    // Create a node with attached comment.
+    $this->node = entity_create('node', array('status' => NODE_PUBLISHED, 'type' => 'page'));
+    $this->node->save();
+    entity_reference_create_instance('node', 'page', 'comments', 'Comments', 'comment');
+    $comment = entity_create('comment', array('entity_type' => 'node', 'entity_id' => $this->node->id(), 'field_id' => 'node__comments'));
+    $comment->save();
+
+    $this->comments[] = $comment;
+  }
+
+  /**
+   *  Test scenario all users have access to content.
+   */
+  public function testCommentAccessAll() {
+    user_role_grant_permissions('anonymous', array('access content', 'access comments'));
+
+    $items = array();
+    foreach ($this->comments as $comment) {
+      $items[] = array(
+        'datasource' => 'entity:comment',
+        'item' => $comment,
+        'item_id' => $comment->id(),
+        'text' => $this->randomName(),
+      );
+    }
+    $items = $this->generateItems($items);
+
+    $this->processor->preprocessIndexItems($items);
+    foreach ($items as $item) {
+      $this->assertEqual($item['search_api_access_node'], array('node_access__all'));
+    }
+  }
+
+  /**
+   * Tests scenario where hook_node_grants() take effect.
+   */
+  public function testCommentAccessWithNodeGrants() {
+    db_insert('node_access')
+      ->fields(array(
+        'nid' => $this->node->id(),
+        'langcode' => $this->node->language()->id,
+        'gid' => 1,
+        'realm' => 'test_realm',
+        'grant_view' => 1,
+      ))
+      ->execute();
+
+    $items = array();
+    foreach ($this->comments as $comment) {
+      $items[] = array(
+        'datasource' => 'entity:comment',
+        'item' => $comment,
+        'item_id' => $comment->id(),
+        'text' => $this->randomName(),
+      );
+    }
+    $items = $this->generateItems($items);
+
+    $this->processor->preprocessIndexItems($items);
+    foreach ($items as $item) {
+      $this->assertEqual($item['search_api_access_node'], array('node_access_test_realm:1'));
+    }
+  }
+
+  /**
+   * Populates testing items.
+   *
+   * @param array $items
+   *   Data to populate test items.
+   *    - datasource: The datasource plugin id.
+   *    - item: The item object to be indexed.
+   *    - item_id: Unique item id.
+   *    - text: Textual value of the test field.
+   *
+   * @return array
+   *   The items structure.
+   */
+  public function generateItems (array $items) {
+    $extracted_items = array();
+    foreach ($items as $item) {
+      $extracted_items[$item['datasource'] . '|' . $item['item_id']] = array(
+        '#item' => $item['item'],
+        '#datasource' => $item['datasource'],
+        '#item_id' => $item['item_id'],
+        'id' => array(
+          'type' => 'integer',
+          'original_type' => 'field_item:integer',
+          'value' => array($item['item_id']),
+        ),
+        'field_text' => array(
+          'type' => 'text',
+          'original_type' => 'field_item:string',
+          'value' => array($item['text']),
+        ),
+      );
+    }
+
+    return $extracted_items;
+  }
+
+}
