Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions js/src/admin/extenders/customizeGetRequiredPermissions.tsx
Copy link
Member Author

Choose a reason for hiding this comment

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

This right here is more of a POC of how we can handle cases where other permissions are prerequisites for another permission, similiar like Tags does it for example (permission to create discussion in a tag can only be granted to a new group when the viewForum permissions has that group already.

Open for feedback if this should be extended to cover more cases in fof/upload or if I should abandon this. See https://github.com/flarum/framework/blob/097b3c5baa025b46b1fe96a3191ce25fb9a062e7/extensions/tags/js/src/admin/addTagsPermissionScope.tsx#L34-L46 for a similar implementation in the framework

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import app from 'flarum/admin/app';
import { override } from 'flarum/common/extend';

export default function customizeGetRequiredPermissions() {
override(app, 'getRequiredPermissions', (original, permission: string) => {
const required = original(permission) || [];

if (permission === 'fof-upload.hideSharedUploads') {
if (!required.includes('fof-upload.download')) {
required.push('fof-upload.download');
}
}

return required;
});
}
51 changes: 49 additions & 2 deletions js/src/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import app from 'flarum/admin/app';
import UploadPage from './components/UploadPage';
import extendAdminNav from './extendAdminNav';
import FileListState from '../common/states/FileListState';
import customizeGetRequiredPermissions from './extenders/customizeGetRequiredPermissions';

export * from './components';

Expand Down Expand Up @@ -39,14 +40,59 @@ app.initializers.add('fof-upload', () => {
'moderate',
50
)
.registerPermission(
{
icon: 'fas fa-eye-slash',
label: app.translator.trans('fof-upload.admin.permissions.hide_own_uploads_label'),
permission: 'fof-upload.hideUserUploads',
},
'reply',
42
)
.registerPermission(
{
icon: 'fas fa-eye-slash',
label: app.translator.trans('fof-upload.admin.permissions.hide_uploads_of_others_label'),
permission: 'fof-upload.hideOtherUsersUploads',
},
'moderate',
41
)
.registerPermission(
{
icon: 'fas fa-eye-slash',
label: app.translator.trans('fof-upload.admin.permissions.hide_shared_uploads_label'),
permission: 'fof-upload.hideSharedUploads',
},
'moderate',
40
)
.registerPermission(
{
icon: 'fas fa-trash',
label: app.translator.trans('fof-upload.admin.permissions.delete_uploads_of_others_label'),
label: app.translator.trans('fof-upload.admin.permissions.delete_own_uploads_label'),
permission: 'fof-upload.deleteUserUploads',
},
'reply',
32
)
.registerPermission(
{
icon: 'fas fa-trash',
label: app.translator.trans('fof-upload.admin.permissions.delete_uploads_of_others_label'),
permission: 'fof-upload.deleteOtherUsersUploads',
},
'moderate',
50
31
)
.registerPermission(
{
icon: 'fas fa-trash',
label: app.translator.trans('fof-upload.admin.permissions.delete_shared_uploads_label'),
permission: 'fof-upload.deleteSharedUploads',
},
'moderate',
30
)
.registerPermission(
{
Expand All @@ -66,6 +112,7 @@ app.initializers.add('fof-upload', () => {
);

extendAdminNav();
customizeGetRequiredPermissions();

//app.fileListState = new FileListState();
});
18 changes: 18 additions & 0 deletions migrations/2025_11_07_000000_grant_hide_own_to_members.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Flarum\Database\Migration;
use Flarum\Group\Group;

return Migration::addPermissions([
'fof-upload.hideUserUploads' => Group::MEMBER_ID,
]);
45 changes: 45 additions & 0 deletions migrations/2025_11_07_000001_migrate_permissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Illuminate\Database\Schema\Builder;

return [
'up' => function (Builder $schema) {
$db = $schema->getConnection();

$groups = $db->table('group_permission')
->where('permission', 'fof-upload.deleteUserUploads')
->pluck('group_id');

foreach ($groups as $gid) {
foreach ([
'fof-upload.deleteOtherUsersUploads',
'fof-upload.hideOtherUsersUploads',
] as $perm) {
$db->table('group_permission')->updateOrInsert(
['group_id' => $gid, 'permission' => $perm],
[]
);
}
}
},

'down' => function (Builder $schema) {
$schema->getConnection()
->table('group_permission')
->whereIn('permission', [
'fof-upload.deleteOtherUsersUploads',
'fof-upload.hideOtherUsersUploads',
])
->delete();
},
];
7 changes: 6 additions & 1 deletion resources/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ fof-upload:
download_label: Download files
upload_label: Upload files
view_user_uploads_label: View user uploads
delete_uploads_of_others_label: Delete user uploads
hide_own_uploads_label: Hide own uploads
hide_uploads_of_others_label: Hide uploads of other users
hide_shared_uploads_label: Hide shared uploads
delete_own_uploads_label: Delete own uploads
delete_uploads_of_others_label: Delete uploads of other users
delete_shared_uploads_label: Delete shared uploads
upload_shared_label: Upload shared files
access_shared_label: Access shared files in Media Manager
templates:
Expand Down
30 changes: 26 additions & 4 deletions src/Access/FilePolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
use Flarum\User\Access\AbstractPolicy;
use Flarum\User\User;
use FoF\Upload\File;
use FoF\Upload\Helpers\Util;

class FilePolicy extends AbstractPolicy
{
public function __construct(
protected Util $util
) {
}

public function viewInfo(User $actor, File $file)
{
// for now..
Expand All @@ -26,15 +32,31 @@ public function viewInfo(User $actor, File $file)

public function hide(User $actor, File $file)
{
if (($file->actor?->id === $actor->id || $actor->hasPermission('fof-upload.deleteUserUploads')) && $file->actor !== null) {
return $this->allow();
$isShared = $this->util->isPrivateShared($file) || $file->shared;

if ($isShared) {
return $actor->can('fof-upload.hideSharedUploads') ? $this->allow() : $this->deny();
}

if ($file->actor_id === $actor->id) {
return $actor->can('fof-upload.hideUserUploads') ? $this->allow() : $this->deny();
}

return $actor->can('fof-upload.hideOtherUsersUploads') ? $this->allow() : $this->deny();
}

public function delete(User $actor, File $file)
{
if ($actor->can('fof-upload.deleteUserUploads') && $file->actor !== null) {
return $this->allow();
$isShared = $this->util->isPrivateShared($file) || $file->shared;

if ($isShared) {
return $actor->can('fof-upload.deleteSharedUploads') ? $this->allow() : $this->deny();
}

if ($file->actor_id === $actor->id) {
return $actor->can('fof-upload.deleteUserUploads') ? $this->allow() : $this->deny();
}

return $actor->can('fof-upload.deleteOtherUsersUploads') ? $this->allow() : $this->deny();
}
}
26 changes: 9 additions & 17 deletions src/Commands/DeleteFileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Flarum\Foundation\ValidationException;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Exception\PermissionDeniedException;
use FoF\Upload\Adapters\Manager;
use FoF\Upload\File;
use FoF\Upload\Helpers\Util;
Expand Down Expand Up @@ -42,30 +43,21 @@ public function __construct(

public function handle(DeleteFile $command): void
{
$privateShared = $this->util->isPrivateShared($command->file);

if ($privateShared || $command->file->shared) {
$command->actor->assertCan('fof-upload.upload-shared-files');
} else {
// We don't currently have a permission for this, so we'll just use admin.
$command->actor->assertAdmin();
if (!$command->actor->can('delete', $command->file)) {
throw new PermissionDeniedException();
}

$success = false;
$privateShared = $this->util->isPrivateShared($command->file);

// Delete the file from storage.
if ($privateShared) {
$success = $this->deleteSharedFile($command->file);
} else {
$success = $this->deleteFileViaAdaptor($command->file);
}
$success = $privateShared
? $this->deleteSharedFile($command->file)
: $this->deleteFileViaAdaptor($command->file);

if ($success === false) {
throw new ValidationException(['file' => 'Could not delete file.']);
} else {
// Delete the file record from the database.
$command->file->delete();
}

$command->file->delete();
}

protected function deleteSharedFile(File $file): bool
Expand Down
28 changes: 19 additions & 9 deletions tests/integration/api/HideFilesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Flarum\Extend;
use Flarum\Foundation\Paths;
use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use FoF\Upload\File;
use FoF\Upload\Tests\EnhancedTestCase;
Expand All @@ -32,19 +33,29 @@ public function setUp(): void
$this->prepareDatabase([
'users' => [
$this->normalUser(),
['id' => 3, 'username' => 'normal2', 'email' => '[email protected]'],
['id' => 4, 'username' => 'moderator', 'email' => '[email protected]'],
['id' => 3, 'username' => 'normal2', 'email' => '[email protected]', 'is_email_confirmed' => 1],
['id' => 4, 'username' => 'moderator', 'email' => '[email protected]', 'is_email_confirmed' => 1],
],
'fof_upload_files' => [
['id' => 1, 'base_name' => 'test_file.abc', 'uuid' => 'abc-123', 'path' => 'path/test_file.abc', 'url' => 'http://localhost/test_file.abc', 'type' => 'test/file', 'size' => 123, 'upload_method' => 'local', 'actor_id' => 2, 'shared' => false],
['id' => 2, 'base_name' => 'test_file2.abc', 'uuid' => 'def-456', 'path' => 'path/test_file2.abc', 'url' => 'http://localhost/test_file2.abc', 'type' => 'test/file', 'size' => 123, 'upload_method' => 'local', 'shared' => true],
],
'group_user' => [
['user_id' => 4, 'group_id' => 4],
['user_id' => 4, 'group_id' => Group::MODERATOR_ID],
],
'group_permission' => [
['group_id' => 4, 'permission' => 'fof-upload.deleteUserUploads'],
['group_id' => 4, 'permission' => 'fof-upload.viewUserUploads'],
// General permissions
['group_id' => Group::MEMBER_ID, 'permission' => 'fof-upload.download'],
['group_id' => Group::MEMBER_ID, 'permission' => 'fof-upload.viewUserUploads'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'fof-upload.download'],

// Hiding permissions
['group_id' => Group::MEMBER_ID, 'permission' => 'fof-upload.hideUserUploads'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'fof-upload.hideOtherUsersUploads'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'fof-upload.hideSharedUploads'],

// Deletion permissions
['group_id' => Group::MODERATOR_ID, 'permission' => 'fof-upload.deleteUserUploads'],
],
]);
}
Expand Down Expand Up @@ -248,9 +259,9 @@ public function admin_can_hide_shared_files()
/**
* @test
*/
public function moderator_cannot_hide_shared_files()
public function moderator_can_hide_shared_files()
{
$uuid = 'def-456';
$uuid = $this->uploadSharedFileAndGetUuid();

$response = $this->send(
$this->request(
Expand All @@ -264,8 +275,7 @@ public function moderator_cannot_hide_shared_files()
]
)
);

$this->assertEquals(403, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode());
}

/**
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/api/SharedFilesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace FoF\Upload\Tests\integration\api;

use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use FoF\Upload\File;
use FoF\Upload\Tests\EnhancedTestCase;
Expand All @@ -36,7 +37,8 @@ public function setUp(): void
['user_id' => 3, 'group_id' => 4],
],
'group_permission' => [
['permission' => 'fof-upload.upload-shared-files', 'group_id' => 4],
['permission' => 'fof-upload.upload-shared-files', 'group_id' => Group::MODERATOR_ID],
['permission' => 'fof-upload.deleteSharedUploads', 'group_id' => Group::MODERATOR_ID],
],
]);
}
Expand Down
Loading