diff --git a/src/Access/LatestRevisionCheck.php b/src/Access/LatestRevisionCheck.php
new file mode 100644
index 0000000..aad55af
--- /dev/null
+++ b/src/Access/LatestRevisionCheck.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Drupal\group\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultNeutral;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Check access to the latest revision in group content.
+ */
+class LatestRevisionCheck implements AccessInterface {
+
+  /**
+   * The content moderation latest version access service.
+   *
+   * @var \Drupal\content_moderation\Access\LatestRevisionCheck
+   */
+  protected $inner;
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs the group latest revision check object.
+   *
+   * @param \Drupal\Core\Routing\Access\AccessInterface $inner
+   *   The inner service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   */
+  public function __construct(AccessInterface $inner, EntityTypeManagerInterface $entity_type_manager) {
+    $this->inner = $inner;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
+    $access = $this->inner->access($route, $route_match, $account);
+    if (!$access->isAllowed()) {
+      // Check for group-specific access.
+      $entity = $this->loadEntity($route, $route_match);
+      if ($entity) {
+        $group_access = $this->checkGroupAccess($entity, $account);
+        $access = $access->orIf($group_access);
+      }
+    }
+    return $access;
+  }
+
+  /**
+   * Determine group-specific access to the latest revision.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to check.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user to check access for.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   Returns allowed access if the entity belongs to a group, and the user
+   *   has both the 'view latest version' and the
+   *   'view unpublished PLUGIN_ID entity' permission in a group it belongs to.
+   */
+  protected function checkGroupAccess(ContentEntityInterface $entity, AccountInterface $account) {
+    $access = AccessResultNeutral::neutral();
+    $plugin_id = 'group_' . $entity->getEntityTypeId() . ':' . $entity->bundle();
+
+    // Only act if there are group content types for this entity bundle.
+    $group_content_types = $this->entityTypeManager->getStorage('group_content_type')->loadByContentPluginId($plugin_id);
+    if (empty($group_content_types)) {
+      return $access;
+    }
+
+    // Load all the group content for this entity.
+    $group_contents = $this->entityTypeManager
+      ->getStorage('group_content')
+      ->loadByProperties([
+        'type' => array_keys($group_content_types),
+        'entity_id' => $entity->id(),
+      ]);
+
+    // If the entity does not belong to any group, we have nothing to say.
+    if (empty($group_contents)) {
+      return $access;
+    }
+
+    /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
+    foreach ($group_contents as $group_content) {
+      $group = $group_content->getGroup();
+      $access = $access->orIf(AccessResult::allowedIf(
+        $group->hasPermission('view latest version for ' . $plugin_id, $account)
+        && $group->hasPermission('view unpublished ' . $plugin_id . ' entity', $account)
+      ));
+      $access->addCacheableDependency($group_content);
+      $access->addCacheableDependency($group);
+    }
+
+    return $access;
+  }
+
+  /**
+   * Copy of content moderation's protected method.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The parametrized route.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface
+   *   returns the Entity in question.
+   *
+   * @throws \Exception
+   *   A generic exception is thrown if the entity couldn't be loaded. This
+   *   almost always implies a developer error, so it should get turned into
+   *   an HTTP 500.
+   */
+  protected function loadEntity(Route $route, RouteMatchInterface $route_match) {
+    $entity_type = $route->getOption('_content_moderation_entity_type');
+
+    if ($entity = $route_match->getParameter($entity_type)) {
+      if ($entity instanceof ContentEntityInterface) {
+        return $entity;
+      }
+    }
+    throw new \Exception(sprintf('%s is not a valid entity route. The LatestRevisionCheck access checker may only be used with a route that has a single entity parameter.', $route_match->getRouteName()));
+  }
+
+}
diff --git a/src/GroupServiceProvider.php b/src/GroupServiceProvider.php
new file mode 100644
index 0000000..5c504bb
--- /dev/null
+++ b/src/GroupServiceProvider.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\group;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+use Drupal\group\Access\LatestRevisionCheck;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Service provider for the Group module.
+ *
+ * This is used to alter the content moderation services for integration with
+ * the group module. This can't be done via a normal service declaration as
+ * decorating optional services is not supported.
+ */
+class GroupServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    $modules = $container->getParameter('container.modules');
+    if (isset($modules['content_moderation'])) {
+      // Decorate the latest revision access check.
+      $latest_revision_definition = new Definition(LatestRevisionCheck::class, [
+        new Reference('group.latest_version_access.inner'),
+        new Reference('entity_type.manager'),
+      ]);
+      $latest_revision_definition->setPublic(TRUE);
+      $latest_revision_definition->setDecoratedService('access_check.latest_revision');
+      $container->setDefinition('group.latest_version_access', $latest_revision_definition);
+
+      // Decorate the state transition validation service.
+      $state_transition_definition = new Definition(StateTransitionValidation::class, [
+        new Reference('group.state_transition_validation.inner'),
+        new Reference('content_moderation.moderation_information'),
+        new Reference('current_route_match'),
+        new Reference('entity_type.manager'),
+      ]);
+      $state_transition_definition->setPublic(TRUE);
+      $state_transition_definition->setDecoratedService('content_moderation.state_transition_validation');
+      $container->setDefinition('group.state_transition_validation', $state_transition_definition);
+    }
+  }
+
+}
diff --git a/src/Plugin/GroupContentEnablerBase.php b/src/Plugin/GroupContentEnablerBase.php
index b3c16a0..21db243 100644
--- a/src/Plugin/GroupContentEnablerBase.php
+++ b/src/Plugin/GroupContentEnablerBase.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\workflows\Entity\Workflow;
 
 /**
  * Provides a base class for GroupContentEnabler plugins.
@@ -241,6 +242,47 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
     return $permissions;
   }
 
+  /**
+   * Provides group permissions for the moderation transitions.
+   *
+   * @return array
+   *   An array of group permissions, see ::getPermissions for more info.
+   *
+   * @see GroupContentEnablerInterface::getPermissions()
+   */
+  protected function getGroupModerationPermissions() {
+    $permissions = [];
+    $plugin_id = $this->getPluginId();
+
+    // Allow permissions here and in child classes to easily use the plugin name
+    // and target entity type name in their titles and descriptions.
+    $t_args = [
+      '%plugin_name' => $this->getLabel(),
+      '%entity_type' => $this->getEntityType()->getLowercaseLabel(),
+    ];
+    $defaults = ['title_args' => $t_args, 'description_args' => $t_args];
+
+    // Add the 'view latest version' permission.
+    $permissions['view latest version for ' . $plugin_id] = [
+      'title' => '%plugin_name - View latest version',
+    ] + $defaults;
+
+    /** @var \Drupal\workflows\WorkflowInterface $workflow */
+    foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
+      $defaults['title_args']['%workflow'] = $workflow->label();
+
+      foreach ($workflow->getTypePlugin()->getTransitions() as $transition) {
+        $defaults['title_args']['%transition'] = $transition->label();
+
+        $permissions['use ' . $workflow->id() . ' transition ' . $transition->id() . ' for ' . $plugin_id] = [
+          'title' => '%plugin_name - Use %transition transition from %workflow workflow.',
+        ] + $defaults;
+      }
+    }
+
+    return $permissions;
+  }
+
   /**
    * Provides permissions for the actual entity being added to the group.
    *
@@ -298,6 +340,10 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
     $permissions = $this->getGroupContentPermissions();
     if ($this->definesEntityAccess()) {
       $permissions += $this->getTargetEntityPermissions();
+      // @TODO: Dependency injection.
+      if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
+        $permissions += $this->getGroupModerationPermissions();
+      }
     }
     return $permissions;
   }
diff --git a/src/StateTransitionValidation.php b/src/StateTransitionValidation.php
new file mode 100644
index 0000000..7ea7771
--- /dev/null
+++ b/src/StateTransitionValidation.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace Drupal\group;
+
+use Drupal\content_moderation\ModerationInformationInterface;
+use Drupal\content_moderation\StateTransitionValidationInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\workflows\Transition;
+
+/**
+ * Validates whether a certain state transition is allowed.
+ */
+class StateTransitionValidation implements StateTransitionValidationInterface {
+
+  /**
+   * The content moderation state transition validation service.
+   *
+   * @var \Drupal\content_moderation\StateTransitionValidationInterface
+   */
+  protected $inner;
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The moderation information service.
+   *
+   * @var \Drupal\content_moderation\ModerationInformationInterface
+   */
+  protected $moderationInformation;
+
+  /**
+   * The route match service.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Constructs the group state transition validation object.
+   *
+   * @param \Drupal\content_moderation\StateTransitionValidationInterface $inner
+   *   The content moderation state transition validation service.
+   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information
+   *   The moderation information service.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   */
+  public function __construct(StateTransitionValidationInterface $inner, ModerationInformationInterface $moderation_information, RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager) {
+    $this->inner = $inner;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->moderationInformation = $moderation_information;
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) {
+    // New entities are not yet associated with a group, but if we are using the
+    // wizard we can discover the group from the route parameters.
+    if ($entity->isNew()) {
+      $group = $this->getNewEntityGroup();
+      if ($group) {
+        return array_merge(
+          $this->allowedTransitions($user, $entity, [$group]),
+          $this->inner->getValidTransitions($entity, $user)
+        );
+      }
+      return $this->inner->getValidTransitions($entity, $user);
+    }
+
+    // Only act if there are group content types for this entity bundle.
+    $group_content_types = $this->entityTypeManager->getStorage('group_content_type')->loadByContentPluginId($this->getPluginId($entity));
+    if (empty($group_content_types)) {
+      return $this->inner->getValidTransitions($entity, $user);
+    }
+
+    // Load all the group content for this entity.
+    $group_contents = $this->entityTypeManager
+      ->getStorage('group_content')
+      ->loadByProperties([
+        'type' => array_keys($group_content_types),
+        'entity_id' => $entity->id(),
+      ]);
+
+    // If the entity does not belong to any group, we have nothing to say.
+    if (empty($group_contents)) {
+      return $this->inner->getValidTransitions($entity, $user);
+    }
+
+    /** @var \Drupal\group\Entity\GroupInterface[] $groups */
+    $groups = [];
+    foreach ($group_contents as $group_content) {
+      /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
+      $group = $group_content->getGroup();
+      $groups[$group->id()] = $group;
+    }
+
+    // Merge the inner service transitions.
+    return array_merge(
+      $this->inner->getValidTransitions($entity, $user),
+      $this->allowedTransitions($user, $entity, $groups)
+    );
+  }
+
+  /**
+   * Create a plugin ID based on an entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *
+   * @return string
+   */
+  protected function getPluginId(ContentEntityInterface $entity) {
+    return 'group_' . $entity->getEntityTypeId() . ':' . $entity->bundle();
+  }
+
+  /**
+   * Run the permissions checks against this set of groups.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $user
+   *   The user to check access to transitions for.
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The moderated entity.
+   * @param array $groups
+   *   The groups the entity belongs to.
+   *
+   * @return \Drupal\workflows\Transition[]
+   *   Array of valid transitions.
+   */
+  protected function allowedTransitions(AccountInterface $user, ContentEntityInterface $entity, array $groups) {
+    // Load the workflow and current state for this entity.
+    $workflow = $this->moderationInformation->getWorkflowForEntity($entity);
+    $current_state = $entity->moderation_state->value ? $workflow->getTypePlugin()->getState($entity->moderation_state->value) : $workflow->getTypePlugin()->getInitialState($workflow, $entity);
+
+    // Check the group access. If you are not granted access for a transition
+    // in any of the groups the entity belongs to, we will check for global
+    // access to that transition instead.
+    $plugin_id = $this->getPluginId($entity);
+    return array_filter($current_state->getTransitions(), function (Transition $transition) use ($workflow, $user, $groups, $plugin_id) {
+      foreach ($groups as $group) {
+        if ($group->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id() . ' for ' . $plugin_id, $user)) {
+          return TRUE;
+        }
+      }
+    });
+  }
+
+  /**
+   * Load the current group from parameters.
+   *
+   * @return \Drupal\group\Entity\GroupInterface|null
+   */
+  protected function getNewEntityGroup() {
+    return $this->routeMatch->getParameter('group');
+  }
+
+}
diff --git a/tests/modules/group_test_content_moderation/group_test_content_moderation.info.yml b/tests/modules/group_test_content_moderation/group_test_content_moderation.info.yml
new file mode 100644
index 0000000..4c9865c
--- /dev/null
+++ b/tests/modules/group_test_content_moderation/group_test_content_moderation.info.yml
@@ -0,0 +1,10 @@
+name: 'Group test content moderation'
+description: 'Test module to ensure the decorated content moderation services are compatible with other modules that decorate these.'
+package: 'Testing'
+type: 'module'
+version: VERSION
+core: '8.x'
+dependencies:
+  - 'content_moderation'
+  - 'group'
+  - 'user'
diff --git a/tests/modules/group_test_content_moderation/group_test_content_moderation.services.yml b/tests/modules/group_test_content_moderation/group_test_content_moderation.services.yml
new file mode 100644
index 0000000..fab2087
--- /dev/null
+++ b/tests/modules/group_test_content_moderation/group_test_content_moderation.services.yml
@@ -0,0 +1,10 @@
+services:
+  # Content moderation decorators to ensure compatibility.
+  group_test_content_moderation.state_transition_validation:
+    class: 'Drupal\group_test_content_moderation\StateTransitionValidation'
+    decorates: content_moderation.state_transition_validation
+    arguments: ['@group_test_content_moderation.state_transition_validation.inner', '@content_moderation.moderation_information']
+  group_test_content_moderation.latest_version_access:
+    class: 'Drupal\group_test_content_moderation\Access\LatestRevisionCheck'
+    decorates: access_check.latest_revision
+    arguments: ['@group_test_content_moderation.latest_version_access.inner', '@entity_type.manager']
diff --git a/tests/modules/group_test_content_moderation/src/Access/LatestRevisionCheck.php b/tests/modules/group_test_content_moderation/src/Access/LatestRevisionCheck.php
new file mode 100644
index 0000000..ac7f8b3
--- /dev/null
+++ b/tests/modules/group_test_content_moderation/src/Access/LatestRevisionCheck.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\group_test_content_moderation\Access;
+
+use Drupal\Core\Access\AccessResultForbidden;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Decorate the content moderation latest revision check.
+ */
+class LatestRevisionCheck implements AccessInterface {
+
+  /**
+   * The content moderation latest version access service.
+   *
+   * @var \Drupal\content_moderation\Access\LatestRevisionCheck
+   */
+  protected $inner;
+
+  /**
+   * Constructs the service decorator.
+   *
+   * @param \Drupal\Core\Routing\Access\AccessInterface $inner
+   *   The inner service.
+   */
+  public function __construct(AccessInterface $inner) {
+    $this->inner = $inner;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
+    $access = $this->inner->access($route, $route_match, $account);
+
+    if ($route->getOption('_explicit_deny')) {
+      return AccessResultForbidden::forbidden('Explicit access denial');
+    }
+
+    return $access;
+  }
+
+}
diff --git a/tests/modules/group_test_content_moderation/src/StateTransitionValidation.php b/tests/modules/group_test_content_moderation/src/StateTransitionValidation.php
new file mode 100644
index 0000000..052b877
--- /dev/null
+++ b/tests/modules/group_test_content_moderation/src/StateTransitionValidation.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\group_test_content_moderation;
+
+use Drupal\content_moderation\ModerationInformationInterface;
+use Drupal\content_moderation\StateTransitionValidationInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Decorate the state transition validation service to ensure compatibility.
+ */
+class StateTransitionValidation implements StateTransitionValidationInterface {
+
+  /**
+   * The inner service.
+   *
+   * @var \Drupal\content_moderation\StateTransitionValidationInterface
+   */
+  protected $inner;
+
+  /**
+   * The moderation information service.
+   *
+   * @var \Drupal\content_moderation\ModerationInformationInterface
+   */
+  protected $moderationInformation;
+
+  /**
+   * Constructs the service decorator.
+   */
+  public function __construct(StateTransitionValidationInterface $inner, ModerationInformationInterface $moderation_information) {
+    $this->inner = $inner;
+    $this->moderationInformation = $moderation_information;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) {
+    $transitions = $this->inner->getValidTransitions($entity, $user);
+
+    // Always allow the archive transition for testing purposes.
+    $workflow = $this->moderationInformation->getWorkflowForEntity($entity);
+    $transitions['archive'] = $workflow->getTypePlugin()->getTransition('archive');
+
+    return $transitions;
+  }
+
+
+}
diff --git a/tests/src/Functional/ContentModerationIntegrationTest.php b/tests/src/Functional/ContentModerationIntegrationTest.php
new file mode 100644
index 0000000..50231b4
--- /dev/null
+++ b/tests/src/Functional/ContentModerationIntegrationTest.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Drupal\Tests\group\Functional;
+
+/**
+ * Tests integration with the core Content Moderation module.
+ *
+ * @group group
+ */
+class ContentModerationIntegrationTest extends GroupBrowserTestBase {
+
+  /**
+   * A group for testing purposes.
+   *
+   * @var \Drupal\group\Entity\GroupInterface
+   */
+  protected $group;
+
+  /**
+   * A normal group member.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $groupMember;
+
+  /**
+   * A non-group member.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $nonGroupMember;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'content_moderation',
+    // @todo Ideally this would test with a non-node content enabler.
+    'gnode',
+    'group',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Set permissions for content moderation in the default group type.
+    $member_permissions = [
+      'update any group_node:article entity',
+      'use editorial transition publish for group_node:article',
+      'view unpublished group_node:article entity',
+      'view latest version for group_node:article',
+    ];
+
+    /** @var \Drupal\group\Entity\GroupTypeInterface $type */
+    $type = $this->entityTypeManager->getStorage('group_type')->load('default');
+    $type->getMemberRole()->grantPermissions($member_permissions)->save();
+
+    // Add the article content type to the group type, and enable workflow.
+    $this->createContentType(['type' => 'article']);
+    /** @var \Drupal\group\Entity\Storage\GroupContentTypeStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage('group_content_type');
+    $storage->createFromPlugin($type, 'group_node:article')->save();
+    /** @var \Drupal\workflows\WorkflowInterface $workflow */
+    $workflow = $this->entityTypeManager->getStorage('workflow')->load('editorial');
+    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
+    $workflow->save();
+
+    // Add a group.
+    $this->group = $this->createGroup();
+
+    $this->nonGroupMember = $this->createUser();
+    $this->groupMember = $this->createUser([
+      // Utilize a global permission here to ensure those are merged in the
+      // access decorator.
+      'use editorial transition create_new_draft',
+    ]);
+
+    $this->group->addMember($this->groupMember);
+
+    node_access_rebuild();
+  }
+
+  /**
+   * Tests access to the latest version tab of non group nodes.
+   *
+   * This is a basic sanity check to ensure the logic in group is not changing
+   * the behavior of content moderation for non-group entities.
+   */
+  public function testLatestVersionAccessNonGroupNode() {
+    $node = $this->createNode(['type' => 'article']);
+
+    // A non-member should not have access to this draft state.
+    $this->drupalLogin($this->nonGroupMember);
+    $this->drupalGet($node->toUrl());
+    $this->assertSession()->statusCodeEquals(403);
+    $this->drupalGet($node->toUrl('edit-form'));
+    $this->assertSession()->statusCodeEquals(403);
+
+    // The group member should not have access.
+    $this->drupalLogin($this->groupMember);
+    $this->drupalGet($node->toUrl());
+    $this->assertSession()->statusCodeEquals(403);
+    $this->drupalGet($node->toUrl('edit-form'));
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Tests access for group nodes.
+   */
+  public function testLatestVersionAccessGroupNode() {
+    $node = $this->createNode(['type' => 'article']);
+    $this->group->addContent($node, 'group_node:article');
+
+    // A non-member should not have access to this draft state.
+    $this->drupalLogin($this->nonGroupMember);
+    $this->drupalGet($node->toUrl());
+    $this->assertSession()->statusCodeEquals(403);
+    $this->drupalGet($node->toUrl('edit-form'));
+    $this->assertSession()->statusCodeEquals(403);
+
+    // The group member should have access.
+    $this->drupalLogin($this->groupMember);
+    $this->drupalGet($node->toUrl());
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet($node->toUrl('edit-form'));
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Create a forward revision and ensure access to that as well.
+    $edit = [
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->drupalGet($node->toUrl('edit-form'));
+    $this->drupalPostForm(NULL, ['title[0][value]' => 'New draft'], t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+  }
+
+}
diff --git a/tests/src/Kernel/ContentModerationIntegrationTest.php b/tests/src/Kernel/ContentModerationIntegrationTest.php
new file mode 100644
index 0000000..b566105
--- /dev/null
+++ b/tests/src/Kernel/ContentModerationIntegrationTest.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace Drupal\Tests\group\Kernel;
+
+use Drupal\Core\Access\AccessResultAllowed;
+use Drupal\Core\Access\AccessResultForbidden;
+use Drupal\Core\Access\AccessResultNeutral;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the overridden access services with additional decorators.
+ *
+ * @group group
+ */
+class ContentModerationIntegrationTest extends GroupKernelTestBase {
+
+  use ContentTypeCreationTrait;
+  use NodeCreationTrait;
+
+  /**
+   * A group.
+   *
+   * @var \Drupal\group\Entity\GroupInterface
+   */
+  protected $group;
+
+  /**
+   * A group member.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $groupMember;
+
+  /**
+   * A group node.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $groupNode;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'content_moderation',
+    'filter',
+    'gnode',
+    'group_test_content_moderation',
+    'node',
+    'text',
+    'workflows',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('content_moderation_state');
+    $this->installEntitySchema('node');
+    $this->installConfig(['content_moderation', 'filter', 'node', 'text']);
+    $this->installSchema('node', ['node_access']);
+    $this->createContentType(['type' => 'article']);
+
+    // Enable workflow.
+    /** @var \Drupal\workflows\WorkflowInterface $workflow */
+    $workflow = $this->entityTypeManager->getStorage('workflow')->load('editorial');
+    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
+    $workflow->save();
+
+    // Setup the group type.
+    $member_permissions = [
+      'use editorial transition publish for group_node:article',
+      'view latest version for group_node:article',
+      'view unpublished group_node:article entity',
+    ];
+    /** @var \Drupal\group\Entity\GroupTypeInterface $type */
+    $type = $this->entityTypeManager->getStorage('group_type')->load('default');
+    $type->getMemberRole()->grantPermissions($member_permissions)->save();
+
+    // Enable node content.
+    /** @var \Drupal\group\Entity\Storage\GroupContentTypeStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage('group_content_type');
+    $storage->createFromPlugin($type, 'group_node:article')->save();
+
+    $this->group = $this->createGroup(['type' => 'default']);
+    $this->groupNode = $this->createNode(['type' => 'article']);
+
+    // Add the global permission to create new drafts. This will verify that
+    // the content moderation part of the service is still working.
+    $this->groupMember = $this->createUser([], ['use editorial transition create_new_draft']);
+    $this->group->addContent($this->groupNode, 'group_node:article');
+    $this->group->addMember($this->groupMember);
+  }
+
+  /**
+   * Tests state transition validation.
+   */
+  public function testStateTransitionValidation() {
+    $validator = $this->container->get('content_moderation.state_transition_validation');
+
+    // Verify that all 3 state transitions (one provided by each module in this
+    // case) are present.
+    // @see \Drupal\group_test_content_moderation\StateTransitionValidation::getValidTransitions
+    $states = $validator->getValidTransitions($this->groupNode, $this->groupMember);
+    $this->assertCount(3, $states);
+    $this->assertArrayHasKey('archive', $states);
+    $this->assertArrayHasKey('create_new_draft', $states);
+    $this->assertArrayHasKey('publish', $states);
+  }
+
+  /**
+   * Tests the latest revision access.
+   */
+  public function testLatestRevisionCheck() {
+    $access_check = $this->container->get('access_check.latest_revision');
+
+    // By default there should be no access granted.
+    $route = $this->prophesize(Route::class);
+    $route->getOption('_content_moderation_entity_type')->willReturn('node');
+    // This option is checked by the testing decorator.
+    // @see \Drupal\group_test_content_moderation\Access\LatestRevisionCheck::access
+    $route->getOption('_explicit_deny')->willReturn(FALSE);
+    $route_match = $this->prophesize(RouteMatchInterface::class);
+    $route_match->getParameter('node')->willReturn($this->groupNode);
+
+    // The content moderation services is checked here since it will return
+    // a forbidden access when no forward revision exists.
+    $this->assertInstanceOf(AccessResultForbidden::class, $access_check->access($route->reveal(), $route_match->reveal(), $this->groupMember));
+
+    // Create a forward revision.
+    $this->groupNode->moderation_state = 'published';
+    $this->groupNode->save();
+    $this->groupNode = $this->entityTypeManager->getStorage('node')->loadUnchanged($this->groupNode->id());
+    $this->groupNode->setTitle($this->randomString());
+    $this->groupNode->moderation_state = 'draft';
+    $this->groupNode->save();
+    $this->groupNode = $this->entityTypeManager->getStorage('node')->loadUnchanged($this->groupNode->id());
+    $route_match->getParameter('node')->willReturn($this->groupNode);
+
+    // The group module should grant access at this point.
+    $this->assertInstanceOf(AccessResultAllowed::class, $access_check->access($route->reveal(), $route_match->reveal(), $this->groupMember));
+
+    // The test decorator will no explicitly forbid access.
+    // @see \Drupal\group_test_content_moderation\Access\LatestRevisionCheck::access
+    $route->getOption('_explicit_deny')->willReturn(TRUE);
+    $access = $access_check->access($route->reveal(), $route_match->reveal(), $this->groupMember);
+    $this->assertInstanceOf(AccessResultForbidden::class, $access);
+    $this->assertEquals('Explicit access denial', $access->getReason());
+  }
+
+}
