1

I have a front-end form where logged users can upload and delete their personal files.

These users have the strict minimum permissions required to interact with this form. They can only Create, Publish and Delete their own entries in the section where the form create these entries.

In order to be able to delete these files from Craft (and not from only the field they're linked in this entry), I have created this custom action in a module:

<?php

namespace modules\mymodule\controllers;

use Craft;
use craft\web\Controller;
use craft\elements\Asset;

class DefaultController extends Controller
{

    public function actionDeleteAsset()
    {
        $this->requireAcceptsJson();
        $this->requireLogin();

        $currentUser = Craft::$app->getUser()->getIdentity();
        if (!$currentUser) {
            return $this->asErrorJson(Craft::t('app', 'Only a logged in user can delete an asset.'));
        } else {
            $request = Craft::$app->getRequest();
            $assetId = $request->getParam('assetId', '');
            $asset = Craft::$app->getAssets()->getAssetById($assetId);

            if (!empty($asset)) {
                if (!Craft::$app->getUser()->checkPermission('deleteFilesAndFoldersInVolume:' . $asset->volumeId)) {
                    return $this->asErrorJson(Craft::t('app', 'You don\'t have permission to delete this asset.'));
                } else {
                    Craft::$app->getElements()->deleteElementById($asset->id, Asset::class);
                    return $this->asJson([
                        'success' => true
                    ]);
                }
            } else {
                return $this->asErrorJson(Craft::t('app', 'This asset does not exists or has already been deleted.'));
            }
        }
    }

}

The problem is that Craft::$app->getUser()->checkPermission('deleteFilesAndFoldersInVolume:' . $asset->volumeId)is not entirely appropriate:

  • I need to add Volume permissions (View volume + Remove files and folders) to these users to allow them to delete their assets from the front-end form ;
  • By adding these permissions, they have access to other users' assets, which is a security problem ;

So how can I check if user has the permissions to delete only his own assets? In other words, this user can only interact with the assets he has uploaded.

  • Why don't you just include a custom permission in your plugin? – Robin Schambach Sep 8 '18 at 21:54
  • I didn't know it was possible. But even with custom permissions, how would you handle that? It seems that there is no user information I can get from the Asset element. So it means that I would have to create one custom different permission per user, and attach it to the file when the user use the front-end form. This would be a lot ot custom permissions for a website that would handle thousands of users, no? – Romain Poirier Sep 10 '18 at 7:50
  • 1
    Just include one single permission if a user is able to delete an asset or not and include a users field to the asset and relate it to all users that may delete it. If the user is not related to the asset -> he can't delete it – Robin Schambach Sep 10 '18 at 8:09
  • Thank you, I will go in this way and integrate it asap. So I'm gonna add a hook on user registration / use of the front-end form to attach the custom permission (docs.craftcms.com/v2/plugins/hooks-reference.html#general-hooks), then I will populate a custom field on file upload. I'll update my question / answer later here. – Romain Poirier Sep 11 '18 at 10:46
  • There are no hooks in Craft 3, only events but yes – Robin Schambach Sep 11 '18 at 11:02
1

Thanks to @RobinSchambach advices, I was able to find a safe solution to handle this permission problematic.

This is possible using two main addings:

  • Register a new custom permission from the plugin
    • This prevents user from be able to access other users assets
  • Add a custom User field on the assets to assign ownership ;
    • Using this we can verify that it was uploaded from user requesting the file deletion

In the plugin Service:

public function linkUserToAssets($assets = [])
{
    $currentUser = Craft::$app->getUser()->getIdentity();
    if ($currentUser && Craft::$app->request->getIsSiteRequest()
        && Craft::$app->request->getIsPost()) {
        foreach ($assets->all() as $asset) {
            if (isset($asset->id) && !empty($asset->id)) {
                $asset = Craft::$app->assets->getAssetById($asset->id);
                if (isset($asset->assetsOwner)) {
                    $asset->assetsOwner = [$currentUser->id];
                    Craft::$app->elements->saveElement($asset);
                }
            }
        }
    }
}

This adds a function to call before each entry is saved, to assign the user's ownership on entry's assets using the assetsOwner custom field.

In the plugin Module:

Event::on(
    UserPermissions::class,
    UserPermissions::EVENT_REGISTER_PERMISSIONS,
    function(RegisterUserPermissionsEvent $event) {
        $event->permissions['User'] = [
            'deleteOwnAssets' => [
                'label' => 'Delete Own Assets'
            ],
        ];
    }
);

This adds a custom permission in the CP which should be added to group of users who will perform this action.

In the plugin Controller:

public function actionDeleteAsset()
{
    $currentUser = Craft::$app->getUser()->getIdentity();
    if ($currentUser) {
        $request = Craft::$app->getRequest();
        $assetId = $request->getBodyParam('assetId');
        $asset = Craft::$app->getAssets()->getAssetById($assetId);

        if ($asset->assetsOwner[0]->id !== $currentUser->id || !Craft::$app->user->checkPermission('deleteOwnAssets')) {
            return $this->asErrorJson(Craft::t('app', self::TEXT_PERMISSION_REQUIRED));
        }
    }
}

This deletes requested assets if the current user was the owner of the asset (using assetsOwner custom field) and has the required custom permission (in this case, deleteOwnAssets).

  • You'll likely receive deprecation errors with that code and there are a few "mistakes"/useless calls. If you want we can talk in detail at Slack about it – Robin Schambach Sep 25 '18 at 10:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.