flysystem icon indicating copy to clipboard operation
flysystem copied to clipboard

Directory permissions of implicitly created directories not set correctly for local adapter

Open nagmat84 opened this issue 2 years ago • 5 comments

Bug Report

Q A
Flysystem Version 3.10.2
Adapter Name LocalFilesystemAdapter
Adapter version 3.10.2

Summary

Several methods (e.g. writeStream) implicitly and recursively create the parent directories, if they don't exist yet. Internally, this is ensured by calling ensureRootDirectoryExists. This method creates the missing directories by calling mkdir($location, $permissions, true). While $permissions is set according the the configuration file, this method does not take into account that $permissions is ANDed with the current umask value, see https://www.php.net/manual/en/function.umask.php.

IMHO, the method should either explicitly set permissions after the directories have been created or temporarily set the umask value to 07777 and restore the previous umask value afterwards.

Moreover, the method createDirectory seems odd, too. If it is called for an existing directory, then the method explicitly sets the permissions via setPermissions and returns. If the directory does not yet exist, the methods creates the directory, but does not set its permissions. Effectively, this means one has to call the method createDirectoy twice in order to ensure correct permissions: the first time to create the directory, the second time to set its permissions.

nagmat84 avatar Oct 27 '22 10:10 nagmat84

This behaviour is the same for all adapter, implicit directory creation is only done when the directory does not exist and permission are used for missing directories. As for the umask, this is something that cannot be standardised as the function is not safe for multithreaded environments.

frankdejonge avatar Oct 27 '22 11:10 frankdejonge

If you require a different umask, you can either do so by decorating the adapter, or setting the umask in a always loaded php file.

frankdejonge avatar Oct 27 '22 11:10 frankdejonge

As for the umask, this is something that cannot be standardised as the function is not safe for multithreaded environments.

I totally agree. There is also a warning in the PHP doc which states that usmask should not be used, but that chmod should be used explicitly after the file or directory has been created.

This behaviour is the same for all adapter, implicit directory creation is only done when the directory does not exist and permission are used for missing directories.

This is the point at which I would like to object, in particular the last part of the sentence which states that "permission are used for missing directories". IMHO this is not what happens, at least not consistently across the library.

A couple of functions internally call setPermissions which implements the chmod approach mentioned above, these methods are createDirectory, setVisibility, writeToFile. This means if one calls one of those functions, the resulting effective permissions are exactly the permissions which have been passed to those methods or are configured.

Another bunch of functions do not call setPermissions and hence, they are affected by the current umask value, namely ensureDirectoryExists and those methods which rely on ensureDirectoryExist, namely createDirectory (upon the first call) and writeToFile for all intermediate directories. This means if one calls one of those functions, the resulting effective permissions are the permissions which have been passed to those methods ANDed with the current umask value.

Surely, one could make the design decision that passed permissions are subject to the umask value. But IMHO this design decision should be made consistently such that the library always exhibits the same external behavior. Currently, the outcome of createDirectory depends on whether the parent directories already exist or not. (Same holds for writeToFile). This makes the result of this methods somewhat unpredictable (unless umask equals zero of course).

nagmat84 avatar Oct 27 '22 11:10 nagmat84

I have the same (or a similar) issue with the LocalFilesystemAdapter, also in v2.

The problem is that setVisibility() will only be called if the Filesystem instance Config key visibility is set

  • https://github.com/thephpleague/flysystem/blob/712478fdaab4ad79b14e01d3f378208555f33e56/src/Local/LocalFilesystemAdapter.php#L123-L124

or when you pass the Config option as 3. Parameter to the write method.

  • https://github.com/thephpleague/flysystem/blob/712478fdaab4ad79b14e01d3f378208555f33e56/src/Local/LocalFilesystemAdapter.php#L96

To solve this, you may try to create a Filesystem with an extra option:

// The internal adapter
$adapter = new \League\Flysystem\Local\LocalFilesystemAdapter(
    __DIR__.'/root/directory/', 
    PortableVisibilityConverter::fromArray([...]),
);

// Extra
$filesystemConfig = [
    'visibility' => \League\Flysystem\Visibility::PUBLIC,
];

// The FilesystemOperator
$filesystem = new \League\Flysystem\Filesystem($adapter, $filesystemConfig);

odan avatar Nov 16 '22 22:11 odan

It actually worked in v1 but disappeared in v3: https://github.com/thephpleague/flysystem/blob/1.x/src/Adapter/Local.php#L383

I did not notice any BC break announcement in changelog.md nor in the GH releases section.

escopecz avatar Apr 28 '23 10:04 escopecz