diff --git a/moderation_dashboard.module b/moderation_dashboard.module
index abb5699..fd38323 100644
--- a/moderation_dashboard.module
+++ b/moderation_dashboard.module
@@ -5,7 +5,6 @@
  * Contains hooks for the moderation_dashboard module.
  */
 
-use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Url;
@@ -98,21 +97,7 @@ function moderation_dashboard_preprocess_block(&$variables) {
 
 /**
  * Custom access callback for our page manager page.
- *
- * If no Content Type has moderation enabled, users cannot the dashboard.
  */
 function _moderation_dashboard_page_access() {
-  /** @var \Drupal\content_moderation\ModerationInformation $moderation_info */
-  $moderation_info = \Drupal::service('content_moderation.moderation_information');
-  /** @var \Drupal\Core\Entity\EntityTypeBundleInfo $bundle_info */
-  $bundle_info = \Drupal::service('entity_type.bundle.info');
-  $entity_type = \Drupal::entityTypeManager()->getDefinition('node');
-
-  foreach ($bundle_info->getBundleInfo('node') as $bundle => $info) {
-    if ($moderation_info->shouldModerateEntitiesOfBundle($entity_type, $bundle)) {
-      return AccessResult::allowed();
-    }
-  }
-
-  return AccessResult::forbidden();
+  return Drupal::service('moderation_dashboard.page_access')->getAccessResult();
 }
diff --git a/moderation_dashboard.permissions.yml b/moderation_dashboard.permissions.yml
index 6909ed7..369a78d 100644
--- a/moderation_dashboard.permissions.yml
+++ b/moderation_dashboard.permissions.yml
@@ -1,3 +1,5 @@
 use moderation dashboard:
   title: 'Use the Moderation Dashboard'
   description: 'This permission is necessary to access the Moderation Dashboard.'
+view any moderation dashboard:
+  title: 'View any Moderation Dashboard'
diff --git a/moderation_dashboard.services.yml b/moderation_dashboard.services.yml
index 3175d83..84d8dc4 100644
--- a/moderation_dashboard.services.yml
+++ b/moderation_dashboard.services.yml
@@ -8,3 +8,11 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@current_user']
+  moderation_dashboard.page_access:
+    class: Drupal\moderation_dashboard\PageAccess
+    arguments:
+      - '@content_moderation.moderation_information'
+      - '@entity_type.bundle.info'
+      - '@entity_type.manager'
+      - '@current_user'
+      - '@current_route_match'
diff --git a/src/PageAccess.php b/src/PageAccess.php
new file mode 100644
index 0000000..7b748bc
--- /dev/null
+++ b/src/PageAccess.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Drupal\moderation_dashboard;
+
+use Drupal\content_moderation\ModerationInformationInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Service for calculating custom access of the dashboard page.
+ */
+class PageAccess {
+
+  /**
+   * The moderation information service.
+   *
+   * @var \Drupal\content_moderation\ModerationInformationInterface
+   */
+  protected $moderationInformation;
+
+  /**
+   * The bundle information service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $bundleInfo;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * PageAccess constructor.
+   *
+   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information
+   *   The moderation information service.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
+   *   The bundle information service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current user.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   */
+  public function __construct(ModerationInformationInterface $moderation_information, EntityTypeBundleInfoInterface $bundle_info, EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user, RouteMatchInterface $route_match) {
+    $this->moderationInformation = $moderation_information;
+    $this->bundleInfo = $bundle_info;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->currentUser = $current_user;
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * Returns the result of the access check.
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   *   The access result.
+   */
+  public function getAccessResult() {
+    if (!$this->hasModeratedContentType()) {
+      return AccessResult::forbidden();
+    }
+
+    return $this->canViewDashboard() ? AccessResult::allowed() : AccessResult::forbidden();
+  }
+
+  /**
+   * Returns whether there is any moderated content type.
+   *
+   * @return bool
+   *   TRUE if a moderated content type exists, FALSE otherwise.
+   */
+  public function hasModeratedContentType() {
+    $entity_type = $this->entityTypeManager->getDefinition('node');
+
+    foreach ($this->bundleInfo->getBundleInfo('node') as $bundle => $info) {
+      if ($this->moderationInformation->shouldModerateEntitiesOfBundle($entity_type, $bundle)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Returns whether the current user can view the current dashboard.
+   *
+   * @return bool
+   *   TRUE if the current user can view the current dashboard, FALSE otherwise.
+   */
+  public function canViewDashboard() {
+    $owner = $this->routeMatch->getParameter('user');
+
+    if (is_numeric($owner)) {
+      $owner = $this->entityTypeManager
+        ->getStorage('user')
+        ->load($owner);
+    }
+    if (!$owner instanceof UserInterface) {
+      return FALSE;
+    }
+
+    // If the given user doesn't have a dashboard, nobody can view it.
+    if (!$owner->hasPermission('use moderation dashboard')) {
+      return FALSE;
+    }
+
+    // If the current user is on their own dashboard, they can view it.
+    if ($this->currentUser->id() === $owner->id()) {
+      return TRUE;
+    }
+
+    // But they can only view the dashboard of others with another permission.
+    return $this->currentUser->hasPermission('view any moderation dashboard');
+  }
+
+}
diff --git a/src/Routing/RouteSubscriber.php b/src/Routing/RouteSubscriber.php
index 9a85fa9..c6de399 100644
--- a/src/Routing/RouteSubscriber.php
+++ b/src/Routing/RouteSubscriber.php
@@ -16,7 +16,6 @@ class RouteSubscriber extends RouteSubscriberBase {
    */
   protected function alterRoutes(RouteCollection $collection) {
     if ($route = $collection->get('page_manager.page_view_moderation_dashboard_moderation_dashboard-panels_variant-0')) {
-      $route->setRequirement('_permission', 'use moderation dashboard');
       $route->setRequirement('_custom_access', '_moderation_dashboard_page_access');
     }
   }
diff --git a/tests/src/Functional/ModerationDashboardPermissionTest.php b/tests/src/Functional/ModerationDashboardPermissionTest.php
new file mode 100644
index 0000000..3069408
--- /dev/null
+++ b/tests/src/Functional/ModerationDashboardPermissionTest.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\Tests\moderation_dashboard\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
+
+/**
+ * Tests moderation dashboard permissions.
+ *
+ * @group moderation_dashboard
+ */
+class ModerationDashboardPermissionTest extends BrowserTestBase {
+
+  use ContentModerationTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'moderation_dashboard',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create a moderated entity type.
+    $this->drupalCreateContentType([
+      'type' => 'page',
+    ]);
+    $workflow = $this->createEditorialWorkflow();
+    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
+    $workflow->save();
+  }
+
+  /**
+   * The test data.
+   *
+   * @var array
+   */
+  protected $canViewOwnDashboardCases = [
+    [
+      'permissions' => ['use moderation dashboard'],
+    ],
+    [
+      'permissions' => ['view any moderation dashboard', 'use moderation dashboard'],
+    ],
+  ];
+
+  /**
+   * Tests if a user can view their dashboard with permission.
+   */
+  public function testCanViewOwnDashboard() {
+    foreach ($this->canViewOwnDashboardCases as $i => $testCase) {
+      $user = $this->createUser($testCase['permissions']);
+      $this->drupalLogin($user);
+      $this->assertSession()->addressEquals("/user/{$user->id()}/moderation/dashboard");
+      $status_code = $this->getSession()->getStatusCode();
+      $message = "#$i: expected 200, got $status_code.";
+      $this->assertEquals(200, $status_code, $message);
+    }
+  }
+
+  /**
+   * The test data.
+   *
+   * @var array
+   */
+  protected $canNotViewOwnDashboardCases = [
+    [
+      'permissions' => [],
+    ],
+    [
+      'permissions' => ['view any moderation dashboard'],
+    ],
+  ];
+
+  /**
+   * Tests that a user can't view their dashboard without permission.
+   */
+  public function testCanNotViewOwnDashboard() {
+    foreach ($this->canNotViewOwnDashboardCases as $i => $testCase) {
+      $user = $this->createUser($testCase['permissions']);
+      $this->drupalLogin($user);
+      $this->drupalGet("/user/{$user->id()}/moderation/dashboard");
+      $status_code = $this->getSession()->getStatusCode();
+      $message = "#$i: expected 403, got $status_code.";
+      $this->assertEquals(403, $status_code, $message);
+    }
+  }
+
+  /**
+   * The test data.
+   *
+   * @var array
+   */
+  protected $canViewOtherDashboardCases = [
+    [
+      'permissions_a' => ['view any moderation dashboard'],
+      'permissions_b' => ['use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard', 'use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard', 'use moderation dashboard'],
+      'permissions_b' => ['use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard', 'use moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard', 'use moderation dashboard'],
+    ],
+  ];
+
+  /**
+   * Tests if a user can view other dashboards with permission.
+   */
+  public function testCanViewOtherDashboard() {
+    foreach ($this->canViewOtherDashboardCases as $i => $testCase) {
+      $user_a = $this->createUser($testCase['permissions_a']);
+      $user_b = $this->createUser($testCase['permissions_b']);
+      $this->drupalLogin($user_a);
+      $this->drupalGet("/user/{$user_b->id()}/moderation/dashboard");
+      $status_code = $this->getSession()->getStatusCode();
+      $message = "#$i: expected 200, got $status_code.";
+      $this->assertEquals(200, $status_code, $message);
+    }
+  }
+
+  /**
+   * The test data.
+   *
+   * @var array
+   */
+  protected $canNotViewOtherDashboardCases = [
+    // User B doesn't have a dashboard, therefore nobody can view it.
+    [
+      'permissions_a' => [],
+      'permissions_b' => [],
+    ],
+    [
+      'permissions_a' => [],
+      'permissions_b' => ['view any moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard'],
+      'permissions_b' => [],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard', 'use moderation dashboard'],
+      'permissions_b' => [],
+    ],
+    [
+      'permissions_a' => ['view any moderation dashboard', 'use moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['use moderation dashboard'],
+      'permissions_b' => [],
+    ],
+    [
+      'permissions_a' => ['use moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard'],
+    ],
+    // User A doesn't have permission to view User B's dashboard.
+    [
+      'permissions_a' => [],
+      'permissions_b' => ['use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => [],
+      'permissions_b' => ['view any moderation dashboard', 'use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['use moderation dashboard'],
+      'permissions_b' => ['use moderation dashboard'],
+    ],
+    [
+      'permissions_a' => ['use moderation dashboard'],
+      'permissions_b' => ['view any moderation dashboard', 'use moderation dashboard'],
+    ],
+  ];
+
+  /**
+   * Tests that a user can't view other dashboards without permission.
+   */
+  public function testCanNotViewOtherDashboard() {
+    foreach ($this->canNotViewOtherDashboardCases as $i => $testCase) {
+      $user_a = $this->createUser($testCase['permissions_a']);
+      $user_b = $this->createUser($testCase['permissions_b']);
+      $this->drupalLogin($user_a);
+      $this->drupalGet("/user/{$user_b->id()}/moderation/dashboard");
+      $status_code = $this->getSession()->getStatusCode();
+      $message = "#$i: expected 403, got $status_code.";
+      $this->assertEquals(403, $status_code, $message);
+    }
+  }
+
+}
