diff --git a/flag.api.php b/flag.api.php
index 1069eb9..08b1749 100644
--- a/flag.api.php
+++ b/flag.api.php
@@ -175,8 +175,13 @@ function hook_flag_validate($action, $flag, $entity_id, $account, $skip_permissi
  *  The user on whose behalf to test the flagging action.
  *
  * @return
- *   Boolean TRUE if the user is allowed to flag/unflag the given entity.
- *   FALSE otherwise.
+ *   One of the following values:
+ *     - TRUE: User has access to the flag.
+ *     - FALSE: User does not have access to the flag.
+ *     - NULL: This module does not perform checks on this flag/action.
+ *
+ *   NOTE: Any module that returns FALSE will prevent the user from
+ *   being able to use the flag.
  *
  * @see hook_flag_access_multiple()
  * @see flag_flag:access()
diff --git a/includes/flag/flag_flag.inc b/includes/flag/flag_flag.inc
index 14368a8..f491b2c 100644
--- a/includes/flag/flag_flag.inc
+++ b/includes/flag/flag_flag.inc
@@ -490,12 +490,18 @@ class flag_flag {
     }
 
     // Allow modules to disallow (or allow) access to flagging.
-    $access_array = module_invoke_all('flag_access', $this, $entity_id, $action, $account);
-
-    foreach ($access_array as $set_access) {
-      if (isset($set_access)) {
-        $access = $set_access;
-      }
+    // We grant access to the flag if both of the following conditions are met:
+    // - No modules say to deny access.
+    // - At least one module says to grant access.
+    // If no module specified either allow or deny, we fall back to the
+    // default access check above.
+    $module_access = module_invoke_all('flag_access', $this, $entity_id, $action, $account);
+    if (in_array(FALSE, $module_access, TRUE)) {
+      $access = FALSE;
+    }
+    elseif (in_array(TRUE, $module_access, TRUE)) {
+      // WARNING: This allows modules to bypass the default access check!
+      $access = TRUE;
     }
 
     return $access;
diff --git a/tests/flag.test b/tests/flag.test
index 4f99ed0..760fd32 100644
--- a/tests/flag.test
+++ b/tests/flag.test
@@ -703,3 +703,160 @@ class FlagLinkTypeConfirmTestCase extends DrupalWebTestCase {
   }
 
 }
+
+/**
+ * Verifies the implementation of hook_flag_access().
+ */
+class FlagHookFlagAccessTestCase extends FlagTestCaseBase {
+
+  /**
+   * Implements getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Flag hook_flag_access() tests'),
+      'description' => t('Checks the ability of modules to use hook_flag_access().'),
+      'group' => t('Flag'),
+    );
+  }
+
+  /**
+   * Implements setUp().
+   */
+  function setUp() {
+    parent::setUp('flag');
+
+    $success = module_enable(array('flagaccesstest'), FALSE);
+
+    // Create a test flag on article nodes.
+    $flag_data = array(
+      'entity_type' => 'node',
+      'name' => 'test_flag',
+      'title' => 'Test Flag',
+      'global' => 0,
+      'types' => array(
+        0 => 'article',
+      ),
+      'flag_short' => 'Flag this item',
+      'flag_long' => '',
+      'flag_message' => '',
+      'unflag_short' => 'Unflag this item',
+      'unflag_long' => '',
+      'unflag_message' => '',
+      'unflag_denied_text' => 'You may not unflag this item',
+      // Use the normal link type as it involves no intermediary page loads.
+      'link_type' => 'normal',
+      'weight' => 0,
+      'show_on_form' => 0,
+      'access_author' => '',
+      'show_contextual_link' => 0,
+      'show_in_links' => array(
+        'full' => 1,
+        'teaser' => 1,
+      ),
+      'i18n' => 0,
+      'api_version' => 3,
+    );
+    $flag = $this->createFlag($flag_data);
+
+    // Create an article node that various users will try to flag.
+    $title = $this->randomName(8);
+    $node = array(
+      'title' => $title,
+      'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
+      'uid' => 1,
+      'type' => 'article',
+      'is_new' => TRUE,
+    );
+    $node = node_submit((object) $node);
+    node_save($node);
+    $this->nid = $node->nid;
+  }
+
+  /**
+   * Verifies that the user sees the flag if a module returns NULL (Ignore).
+   */
+  function testFlagAccessIgnore() {
+    variable_set('FlagHookFlagAccessTestCaseMode', 'ignore');
+    $flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
+    $this->drupalLogin($flag_user);
+
+    // Look at our node.
+    $this->drupalGet('node/' . $this->nid);
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
+
+    // Click the link to flag the node.
+    $this->clickLink(t('Flag this item'));
+
+    $this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
+
+    // Click the link to unflag the node.
+    $this->clickLink(t('Unflag this item'));
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
+  }
+
+  /**
+   * Verifies that the user sees the flag if a module returns TRUE (Allow).
+   */
+  function testFlagAccessAllow() {
+    variable_set('FlagHookFlagAccessTestCaseMode', 'allow');
+    $flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
+    $this->drupalLogin($flag_user);
+
+    // Look at our node.
+    $this->drupalGet('node/' . $this->nid);
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
+
+    // Click the link to flag the node.
+    $this->clickLink(t('Flag this item'));
+
+    $this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
+
+    // Click the link to unflag the node.
+    $this->clickLink(t('Unflag this item'));
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
+  }
+
+  /**
+   * Verifies that the user sees the flag if a module returns TRUE (Allow) to override default access check.
+   */
+  function testFlagAccessAllowOverride() {
+    variable_set('FlagHookFlagAccessTestCaseMode', 'allow');
+    $flag_user = $this->drupalCreateUser(array());
+    $this->drupalLogin($flag_user);
+
+    // Look at our node.
+    $this->drupalGet('node/' . $this->nid);
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
+
+    // Click the link to flag the node.
+    $this->clickLink(t('Flag this item'));
+
+    $this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
+
+    // Click the link to unflag the node.
+    $this->clickLink(t('Unflag this item'));
+
+    $this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
+  }
+
+  /**
+   * Verifies that the user does not see the flag if a module returns FALSE (Deny).
+   */
+  function testFlagAccessDeny() {
+    variable_set('FlagHookFlagAccessTestCaseMode', 'deny');
+    $flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
+    $this->drupalLogin($flag_user);
+
+    // Look at our node.
+    $this->drupalGet('node/' . $this->nid);
+
+    $this->assertNoLink('Flag this item', 0, 'The flag link does not appear on the page.');
+  }
+
+}
diff --git a/tests/flagaccesstest/flagaccesstest.info b/tests/flagaccesstest/flagaccesstest.info
new file mode 100644
index 0000000..3c9c176
--- /dev/null
+++ b/tests/flagaccesstest/flagaccesstest.info
@@ -0,0 +1,6 @@
+name = Flag hook_flag_access test
+description = Tests for hook_flag_access
+core = 7.x
+dependencies[] = flag
+package = Flags
+hidden = TRUE
diff --git a/tests/flagaccesstest/flagaccesstest.module b/tests/flagaccesstest/flagaccesstest.module
new file mode 100644
index 0000000..62a2ce7
--- /dev/null
+++ b/tests/flagaccesstest/flagaccesstest.module
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @file
+ * Module for testing hook_flag_access.
+ */
+
+/**
+ * Implements hook_flag_access.
+ */
+function flagaccesstest_flag_access($flag, $entity_id, $action, $account) {
+  $mode = variable_get('FlagHookFlagAccessTestCaseMode', 'ignore');
+
+  switch ($mode) {
+    case 'ignore':
+      drupal_set_message('hook_flag_access: ignore');
+      return NULL;
+
+    case 'allow':
+      drupal_set_message('hook_flag_access: allow');
+      return TRUE;
+
+    case 'deny':
+      drupal_set_message('hook_flag_access: deny');
+      return FALSE;
+  }
+}
