diff --git a/src/Access/WorkspaceViewCheck.php b/src/Access/WorkspaceViewCheck.php new file mode 100644 index 0000000..c59c0d5 --- /dev/null +++ b/src/Access/WorkspaceViewCheck.php @@ -0,0 +1,29 @@ +access('view', $account))->addCacheableDependency($workspace); + } +} diff --git a/src/Entity/Form/WorkspaceForm.php b/src/Entity/Form/WorkspaceForm.php index e8ab838..e1541ef 100644 --- a/src/Entity/Form/WorkspaceForm.php +++ b/src/Entity/Form/WorkspaceForm.php @@ -106,7 +106,8 @@ class WorkspaceForm extends ContentEntityForm { if ($workspace->id()) { $form_state->setValue('id', $workspace->id()); $form_state->set('id', $workspace->id()); - $form_state->setRedirectUrl($workspace->urlInfo('collection')); + $redirect = $this->currentUser()->hasPermission('administer workspaces') ? $workspace->toUrl('collection') : $workspace->toUrl('canonical'); + $form_state->setRedirectUrl($redirect); } else { drupal_set_message($this->t('The workspace could not be saved.'), 'error'); @@ -114,4 +115,4 @@ class WorkspaceForm extends ContentEntityForm { } } -} \ No newline at end of file +} diff --git a/src/EntityAccess.php b/src/EntityAccess.php index 1019b9d..36aeffa 100644 --- a/src/EntityAccess.php +++ b/src/EntityAccess.php @@ -12,6 +12,22 @@ use Drupal\multiversion\Entity\WorkspaceInterface; class EntityAccess { /** + * @var int + * + * The ID of the default workspace, which has special permission handling. + */ + protected $defaultWorkspaceId; + + /** + * Constructs a new EntityAccess. + * + * @param int $default_workspace + */ + public function __construct($default_workspace) { + $this->defaultWorkspaceId = $default_workspace; + } + + /** * Hook bridge; * * @see hook_entity_access() @@ -31,15 +47,14 @@ class EntityAccess { 'delete' => ['any' => 'delete_any_workspace', 'own' => 'delete_own_workspace'], ]; - $result = AccessResult::allowedIfHasPermission($account, $operations[$operation]['any']) - ->orIf( - AccessResult::allowedIf($workspace->getOwnerId() == $account->id()) - ->andIf(AccessResult::allowedIfHasPermission($account, $operations[$operation]['own'])) - ); + $result =AccessResult::allowedIf($operation == 'view' && $workspace->id() == $this->defaultWorkspaceId) + ->orIf(AccessResult::allowedIfHasPermission($account, $operations[$operation]['any'])) + ->orIf( + AccessResult::allowedIf($workspace->getOwnerId() == $account->id()) + ->andIf(AccessResult::allowedIfHasPermission($account, $operations[$operation]['own'])) + ); return $result; - - //return AccessResult::neutral(); } /** diff --git a/src/Toolbar.php b/src/Toolbar.php index 4d511a0..96ffb4d 100644 --- a/src/Toolbar.php +++ b/src/Toolbar.php @@ -6,6 +6,7 @@ namespace Drupal\workspace; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Link; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; use Drupal\multiversion\Entity\WorkspaceInterface; @@ -34,6 +35,11 @@ class Toolbar { protected $formBuilder; /** + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** * Constructs a new Toolbar. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager @@ -42,12 +48,14 @@ class Toolbar { * The workspace manager service. * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The form builder service. + * @param AccountInterface $current_user + * The current user service. */ - - public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, FormBuilderInterface $form_builder) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, FormBuilderInterface $form_builder, AccountInterface $current_user) { $this->entityTypeManager = $entity_type_manager; $this->workspaceManager = $workspace_manager; $this->formBuilder = $form_builder; + $this->currentUser = $current_user; } /** @@ -122,7 +130,7 @@ class Toolbar { } /** - * Returns a list of all defined workspaces. + * Returns a list of all defined and accessible workspaces. * * Note: This assumes that the total number of workspaces on the site is * very small. If it's actually large this method will have memory issues. @@ -130,6 +138,8 @@ class Toolbar { * @return WorkspaceInterface[] */ protected function allWorkspaces() { - return $this->entityTypeManager->getStorage('workspace')->loadMultiple(); + return array_filter($this->entityTypeManager->getStorage('workspace')->loadMultiple(), function(WorkspaceInterface $workspace) { + return $workspace->access('view', $this->currentUser); + }); } } diff --git a/tests/src/Functional/WorkspacePermissionsTest.php b/tests/src/Functional/WorkspacePermissionsTest.php index f32f03d..bc27375 100644 --- a/tests/src/Functional/WorkspacePermissionsTest.php +++ b/tests/src/Functional/WorkspacePermissionsTest.php @@ -16,6 +16,7 @@ use Drupal\simpletest\BrowserTestBase; * */ class WorkspacePermissionsTest extends BrowserTestBase { + use WorkspaceTestUtilities; public static $modules = ['workspace', 'multiversion']; @@ -169,48 +170,4 @@ class WorkspacePermissionsTest extends BrowserTestBase { $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit"); $this->assertEquals(200, $session->getStatusCode()); } - - /** - * Loads a single workspace by its label. - * - * The UI approach to creating a workspace doesn't make it easy to know what - * the ID is, so this lets us make paths for a workspace after it's created. - * - * @param $label - * The label of the workspace to load. - * @return WorkspaceInterface - */ - protected function getOneWorkspaceByLabel($label) { - /** @var EntityTypeManagerInterface $etm */ - $etm = \Drupal::service('entity_type.manager'); - - /** @var WorkspaceInterface $bears */ - $entity_list = $etm->getStorage('workspace')->loadByProperties(['label' => $label]); - return current($entity_list); - } - - /** - * Creates a new Workspace through the UI. - * - * @param string $label - * The label of the workspace to create. - * @param string $machine_name - * The machine name of the workspace to create. - * - * @throws \Behat\Mink\Exception\ElementNotFoundException - */ - protected function createWorkspaceThroughUI($label, $machine_name) { - $this->drupalGet('/admin/structure/workspace/add'); - - $session = $this->getSession(); - $this->assertEquals(200, $session->getStatusCode()); - - $page = $session->getPage(); - $page->fillField('label', $label); - $page->fillField('machine_name', $machine_name); - $page->findButton(t('Save'))->click(); - - $session->getPage()->hasContent("$label ($machine_name)"); - - } } diff --git a/tests/src/Functional/WorkspaceTestUtilities.php b/tests/src/Functional/WorkspaceTestUtilities.php new file mode 100644 index 0000000..3a811a3 --- /dev/null +++ b/tests/src/Functional/WorkspaceTestUtilities.php @@ -0,0 +1,63 @@ +getStorage('workspace')->loadByProperties(['label' => $label]); + return current($entity_list); + } + + /** + * Creates a new Workspace through the UI. + * + * @param string $label + * The label of the workspace to create. + * @param string $machine_name + * The machine name of the workspace to create. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + */ + protected function createWorkspaceThroughUI($label, $machine_name) { + $this->drupalGet('/admin/structure/workspace/add'); + + $session = $this->getSession(); + $this->assertEquals(200, $session->getStatusCode()); + + $page = $session->getPage(); + $page->fillField('label', $label); + $page->fillField('machine_name', $machine_name); + $page->findButton(t('Save'))->click(); + + $session->getPage()->hasContent("$label ($machine_name)"); + + } +} diff --git a/tests/src/Functional/WorkspaceViewTest.php b/tests/src/Functional/WorkspaceViewTest.php new file mode 100644 index 0000000..4363ce4 --- /dev/null +++ b/tests/src/Functional/WorkspaceViewTest.php @@ -0,0 +1,69 @@ +drupalCreateUser($permissions); + + // Login as a limited-access user and create a workspace. + $this->drupalLogin($editor1); + + $this->createWorkspaceThroughUI('Bears', 'bears'); + + // Now edit that same workspace; We should be able to do so. + + $bears = $this->getOneWorkspaceByLabel('Bears'); + + // Now login as a different user and create a workspace. + + $editor2 = $this->drupalCreateUser($permissions); + + $this->drupalLogin($editor2); + $session = $this->getSession(); + + $this->createWorkspaceThroughUI('Packers', 'packers'); + + $packers = $this->getOneWorkspaceByLabel('Packers'); + + // Load the activate form for the Bears workspace. It should fail because + // the workspace belongs to someone else. + $this->drupalGet("admin/structure/workspace/{$bears->id()}/activate"); + $this->assertEquals(403, $session->getStatusCode()); + + // But editor 2 should be able to activate the Packers workspace. + $this->drupalGet("admin/structure/workspace/{$packers->id()}/activate"); + $this->assertEquals(200, $session->getStatusCode()); + } +} diff --git a/workspace.permissions.yml b/workspace.permissions.yml index 860dada..f9a81a5 100644 --- a/workspace.permissions.yml +++ b/workspace.permissions.yml @@ -26,4 +26,4 @@ delete_any_workspace: description: Delete a workspace and all content revisions within it, regardless of ownership. permission_callbacks: - - \Drupal\workspace\EntityAccess::workspacePermissions + - workspace.entity_access::workspacePermissions diff --git a/workspace.routing.yml b/workspace.routing.yml index 152febd..1c54ab9 100644 --- a/workspace.routing.yml +++ b/workspace.routing.yml @@ -35,7 +35,7 @@ entity.workspace.activate_form: options: _admin_route: TRUE requirements: - _permission: 'administer workspaces' + _workspace_view: 'TRUE' # WorkspaceType routing definition entity.workspace_type.collection: diff --git a/workspace.services.yml b/workspace.services.yml index c4d24fd..18ef9ff 100644 --- a/workspace.services.yml +++ b/workspace.services.yml @@ -16,7 +16,7 @@ services: arguments: ['@multiversion.manager', '@entity_type.manager'] workspace.toolbar: class: Drupal\workspace\Toolbar - arguments: ['@entity_type.manager', '@workspace.manager', '@form_builder'] + arguments: ['@entity_type.manager', '@workspace.manager', '@form_builder', '@current_user'] workspace.route_subscriber: class: Drupal\workspace\Routing\RouteSubscriber arguments: ['@entity.manager'] @@ -34,3 +34,8 @@ services: - { name: event_subscriber } workspace.entity_access: class: Drupal\workspace\EntityAccess + arguments: ['%workspace.default%'] + access_check.workspace_view: + class: Drupal\workspace\Access\WorkspaceViewCheck + tags: + - { name: access_check, applies_to: _workspace_view }