Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Issue #27: Fix permissions #29

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b28b42d
Issue #27: Clean up and reorder imports.
donquixote Sep 9, 2024
dcaf2e8
Issue #27: Replace the 'is_anonymous' pseudo-permission.
donquixote Sep 9, 2024
6d0f91c
Issue #27: Replace usage of pseudo-permissions with Drupal permissions.
donquixote Sep 9, 2024
67f330a
Issue #27: Remove the 'User permissions' section from README.md, for …
donquixote Sep 9, 2024
48e64d5
Issue #27: Simplify a condition in wopiCheckFileInfo().
donquixote Sep 9, 2024
864381b
Issue #27: Require PHP >= 8.1 to allow for 'readonly'.
donquixote Sep 9, 2024
958e303
Issue #27: Add a config schema.
donquixote Sep 10, 2024
97dae81
Issue #27: Replace access checks with entity access operation.
donquixote Sep 9, 2024
b388a45
Issue #27: Use the media entity from the field items in the field for…
donquixote Sep 11, 2024
54f92b9
Issue #27: Rename $element -> $elements in the field formatter.
donquixote Sep 11, 2024
7f3eb61
Issue #27: Check Collabora access in the field formatter.
donquixote Sep 11, 2024
8f144b6
Issue #27: Check the new operations for entity operation links, inste…
donquixote Sep 12, 2024
f1ca49f
Issue #27: Move controller access checks to the route definition for …
donquixote Sep 12, 2024
71c2c47
Issue #27: Replace global permissions with per-bundle perms and 'edit…
donquixote Sep 9, 2024
8e39e10
Issue #27: Test access to routes.
donquixote Sep 13, 2024
d424b78
Issue #27: Add some `@todo` for possibly empty variables.
donquixote Sep 12, 2024
46b5b5f
Issue #27: Put a doc comment on WopiController::permissionDenied(), d…
donquixote Sep 18, 2024
ca80fee
Issue #27: Remove uneeded classes and module in tests.
AaronGilMartinez Sep 19, 2024
805efa2
Issue #27: Add license.
AaronGilMartinez Oct 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,23 +119,6 @@ You also must set the viewer for this kind of media.
_Collabora Online Preview_.
- Click _Save_.

### User permissions

There are three levels of permissions. _Administrator_,
_Collaborator_, and _Viewer_, in order of most privileges to the
least. Each lesser privilege is included in the higher level.
_Administrator_ includes _Collaborator_, and _Collaborator_ includes
_Viewer_.

By default, the user role `administrator` is mapped to the Collabora
Online administrator. This allow accessing the console.

The user role `authenticated` is the default permission for
collaboration, i.e. edit a document with Collabora Online.

The user role `anonymous` by default disallows editing and is the
minimal permission for viewing documents in Collabora Online.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can rethink this section. For now I just removed it to avoid confusion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now it is unclear how these new permissions work. Is it possible to have a short write up here?

### Other configuration

If you need to change the accepted extensions to upload, go to
Expand Down
44 changes: 38 additions & 6 deletions collabora_online.module
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use Drupal\Core\Entity\EntityInterface;
use Drupal\collabora_online\Cool\CoolUtils;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\media\MediaInterface;

/**
* Implements hook_theme().
Expand Down Expand Up @@ -84,10 +86,10 @@ function collabora_online_entity_operation(EntityInterface $entity) {
return [];
}

/** @var \Drupal\media\MediaInterface $media */
$media = $entity;

$account = \Drupal::currentUser()->getAccount();
if (!$entity->access("view", $account)) {
if (!$media->access('preview in collabora')) {
return [];
}

Expand All @@ -106,7 +108,7 @@ function collabora_online_entity_operation(EntityInterface $entity) {
]
];

if (CoolUtils::canEdit($file) && $media->access("update", $account)) {
if (CoolUtils::canEdit($file) && $media->access('edit in collabora')) {
$entries['collabora_online_edit'] = [
'title' => t("Edit in Collabora Online"),
'weight' => 50,
Expand All @@ -116,3 +118,33 @@ function collabora_online_entity_operation(EntityInterface $entity) {

return $entries;
}

/**
* Implements hook_ENTITY_TYPE_access() for 'media'.
*
* Checks access for the new media operations provided by this module.
*/
function collabora_online_media_access(MediaInterface $media, string $operation, AccountInterface $account): AccessResultInterface {
$type = $media->bundle();
switch ($operation) {
case 'preview in collabora':
return AccessResult::allowedIfHasPermission($account, "preview $type in collabora");

case 'edit in collabora':
if ($account->hasPermission("edit any $type in collabora")) {
return AccessResult::allowed()->cachePerPermissions();
}
if ($account->hasPermission("edit own $type in collabora")) {
// Use '==' because Drupal sometimes loads integers as strings.
$is_owner = ($account->id() && $account->id() == $media->getOwnerId());
return AccessResult::allowedIf($is_owner)
->cachePerPermissions()
->cachePerUser()
->addCacheableDependency($media);
}
return AccessResult::neutral()->cachePerPermissions();

default:
return AccessResult::neutral();
}
}
6 changes: 6 additions & 0 deletions collabora_online.permissions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
administer collabora instance:
title: 'Administer the Collabora instance'
restrict access: true

permission_callbacks:
- \Drupal\collabora_online\CollaboraMediaPermissions::mediaTypePermissions
8 changes: 6 additions & 2 deletions collabora_online.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ collabora-online.view:
defaults:
_controller: '\Drupal\collabora_online\Controller\ViewerController::editor'
_title: 'Collabora Online'
# The controller method has a boolean parameter '$edit'.
edit: false
options:
parameters:
Expand All @@ -11,13 +12,15 @@ collabora-online.view:
edit:
type: boolean
requirements:
_permission: 'access content'
media: \d+
_entity_access: 'media.preview in collabora'

collabora-online.edit:
path: '/cool/edit/{media}'
defaults:
_controller: '\Drupal\collabora_online\Controller\ViewerController::editor'
_title: 'Collabora Online'
# The controller method has a boolean parameter '$edit'.
edit: true
options:
parameters:
Expand All @@ -26,7 +29,8 @@ collabora-online.edit:
edit:
type: boolean
requirements:
_permission: 'access content'
media: \d+
_entity_access: 'media.edit in collabora'

collabora-online.settings:
path: '/admin/config/cool/settings'
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
}
],
"require": {
"php": ">=8.1",
"drupal/jwt": "^2.0"
}
}
6 changes: 0 additions & 6 deletions config/install/collabora_online.settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,3 @@ cool:
disable_cert_check: false
# Allow fullscreen - Enabled by default for functionality.
allowfullscreen: true
# The user role for collaborators.
viewer_role: anonymous
# The user role for collaborators.
collaborator_role: authenticated
# The user role for adminstrators.
administrator_role: administrator
23 changes: 23 additions & 0 deletions config/schema/collabora_online.schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
collabora_online.settings:
type: config_object
label: 'Settings'
mapping:
cool:
type: config_object
label: 'Cool'
mapping:
server:
type: uri
label: 'The address of the COOL server.'
wopi_base:
type: uri
label: 'The WOPI base'
key_id:
type: string
label: 'The JWT key id'
access_token_ttl:
type: integer
label: 'Access Token TTL'
disable_cert_check:
type: boolean
label: 'Disable cert checks.'
90 changes: 90 additions & 0 deletions src/CollaboraMediaPermissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php
hfiguiere marked this conversation as resolved.
Show resolved Hide resolved
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

namespace Drupal\collabora_online;

use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\BundlePermissionHandlerTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\media\MediaTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides permissions for Collabora per media type.
*
* @see \Drupal\media\MediaPermissions
*/
class CollaboraMediaPermissions implements ContainerInjectionInterface {

use AutowireTrait;
use BundlePermissionHandlerTrait;
use StringTranslationTrait;

/**
* Constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager service.
*/
public function __construct(
protected readonly EntityTypeManagerInterface $entityTypeManager,
) {}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity_type.manager'));
}

/**
* Returns an array of media type permissions.
*
* @return array
* The media type permissions.
*
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
*/
public function mediaTypePermissions(): array {
// Generate media permissions for all media types.
$media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple();
return $this->generatePermissions($media_types, [$this, 'buildPermissions']);
}

/**
* Returns a list of permissions for a given media type.
*
* @param \Drupal\media\MediaTypeInterface $type
* The media type.
*
* @return array
* An associative array of permission names and descriptions.
*/
protected function buildPermissions(MediaTypeInterface $type) {
$type_id = $type->id();
$type_params = ['%type_name' => $type->label()];

return [
"preview $type_id in collabora" => [
'title' => $this->t('%type_name: Preview media file in Collabora', $type_params),
],
"edit own $type_id in collabora" => [
'title' => $this->t('%type_name: Edit own media file in Collabora', $type_params),
],
"edit any $type_id in collabora" => [
'title' => $this->t('%type_name: Edit any media file in Collabora', $type_params),
],
];
}

}
20 changes: 1 addition & 19 deletions src/Controller/ViewerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@

namespace Drupal\collabora_online\Controller;

use Drupal\collabora_online\Cool\CoolUtils;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\RendererInterface;
use Drupal\collabora_online\Cool\CoolUtils;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

/**
* Provides route responses for the Collabora module.
Expand Down Expand Up @@ -55,22 +53,6 @@ public function editor(Media $media, $edit = false) {
'closebutton' => 'true',
];

$user = \Drupal::currentUser();
$permissions = CoolUtils::getUserPermissions($user);

if (!$permissions['is_viewer']) {
$error_msg = 'Authentication failed.';
\Drupal::logger('cool')->error($error_msg);
return new Response(
$error_msg,
Response::HTTP_FORBIDDEN,
['content-type' => 'text/plain']
);
}

/* Make sure that the user is a collaborator if edit is true */
$edit = $edit && $permissions['is_collaborator'];

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we check about the write permission? The code above make sure that if you get an "editor" but don't have write permission as per drupal, then you get bounced back to a read-only. It also reject if you don't have viewing permission. This is better than WOPI errors in Collabora online.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permissions are set at the entity level and can be found in the collabora_online.module. They handle actions like viewing and editing documents. These permissions are not tied to specific roles, allowing sites to configure which roles can access or modify documents.

$render_array = CoolUtils::getViewerRender($media, $edit, $options);

if (!$render_array || array_key_exists('error', $render_array)) {
Expand Down
28 changes: 20 additions & 8 deletions src/Controller/WopiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@

namespace Drupal\collabora_online\Controller;

use Drupal\collabora_online\Cool\CoolUtils;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\File\FileSystemInterface;
use Drupal\collabora_online\Cool\CoolUtils;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* Provides WOPI route responses for the Collabora module.
*/
class WopiController extends ControllerBase {

static function permissionDenied() {
/**
* Creates a failure response that is understood by Collabora.
*
* @return \Symfony\Component\HttpFoundation\Response
* Response object.
*/
static function permissionDenied(): Response {
return new Response(
'Authentication failed.',
Response::HTTP_FORBIDDEN,
Expand All @@ -42,13 +47,20 @@ function wopiCheckFileInfo(string $id, Request $request) {
return static::permissionDenied();
}

/** @var \Drupal\media\MediaInterface|null $media */
$media = \Drupal::entityTypeManager()->getStorage('media')->load($id);
if (!$media) {
return static::permissionDenied();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment should be changed.

}

$file = CoolUtils::getFileById($id);
$mtime = date_create_immutable_from_format('U', $file->getChangedTime());
// @todo What if the uid in the payload is not set?
// @todo What if $user is NULL?
$user = User::load($jwt_payload->uid);
$permissions = CoolUtils::getUserPermissions($user);
$can_write = $jwt_payload->wri;

if ($can_write && $can_write != $permissions['is_collaborator']) {
if ($can_write && !$media->access('edit in collabora', $user)) {
\Drupal::logger('cool')->error('Token and user permissions do not match.');
return static::permissionDenied();
}
Expand All @@ -69,8 +81,8 @@ function wopiCheckFileInfo(string $id, Request $request) {
'mail' => $user->getEmail(),
],
'UserCanWrite' => $can_write,
'IsAdminUser' => $permissions['is_admin'],
'IsAnonymousUser' => $permissions['is_anonymous']
'IsAdminUser' => $user->hasPermission('administer collabora instance'),
'IsAnonymousUser' => $user->isAnonymous(),
];

$jsonPayload = json_encode($payload);
Expand Down
Loading