config | Loading last commit info... | |
css | ||
js | ||
schemas | ||
.gitignore | ||
README.md | ||
_filesconfig.php.sample |
Files Gallery User Files & Sharing
User Files & Sharing (UFS) is an add-on package for Files Gallery that aims to upgrade Files Gallery into a fully-featured multi-user groupware file management platform.
UFS is currently compatible with Files Gallery 0.10.3. Earlier and later
versions may not work. The update.sh
script will install/upgrade to the
latest compatible version.
What is Files Gallery?
Files Gallery is the web-based file management system of X3 Photo Gallery, but modularized to allow independent use. It is a very solid project: it looks good, has lots of configuration, and is very fast. It is, in its most basic form, a single PHP file, that can be put into any directory, instantly transforming an otherwise boring directory index into an advanced file browser, uploader, and editor.
What is User Files & Sharing?
Files Gallery loads configuration from an administrator-created PHP file. I [ab]used this feature to inject my own functionality into Files Gallery, hijacking various internal functions and stapling on new ones. In some cases, UFS takes complete control, like when dealing with share links. Otherwise, UFS modifies the environment and passes on execution back to Files Gallery.
UFS Features
- Header-based user authentication
- Per-user storage directories
- Basic user management with persistent configuration settings
- File sharing via share links
- Share link access controls, with configurable authentication challenges
- Files Gallery configuration settings can be applied to share links
- PDO backend allows for multiple choices for supporting database
Requirements
UFS is a stateful system and stores user and share link configuration in a database. It uses a SQLite database by default, but as the database is accessed using PDO, any PDO ANSI SQL-compatible backend should work. SQLite and PostgresQL have been tested.
Since UFS provides no user management of its own, some kind of user management backend is required. If you run UFS from behind a reverse proxy, header-based authentication can be utilized.
Files Gallery 0.11.0 introduces native user management. UFS will support this in the future, allowing native user management and login pages. More information.
Why UFS?
I have gone through tons of file sharing and groupware platforms, and each of them suffers from at least one of the same issues:
- Too many features/bloated
- Too complex for simple usage scenarios
- Missing critical features, e.g. share links
- Unoptimized and runs poorly
- Doesn't precisely fit my use case
On that note, my use case is simple:
- Users each get their own private file storage directories
- Users can upload, download, view, and potentially modify files through web UI
- Users can share files and directories with others, with customizable controls
- Other users can perform all the same actions on shared files where allowed
While I liked how it works when I first discovered it, it did lack the features I wanted that would allow it to replace my existing solutions. Given that Files Gallery consists of a single PHP file (with supporting scripts and assets), it seemed simple enough to modify, and the developers allow doing so, I set off to add my own features.
The end result of that work is UFS.
Installation
- Copy
_filesconfig.php.sample
to_filesconfig.php
and edit to your liking - Run
config/upgrade.sh
to install the latest Files Gallery and asset bundle - Serve this repo through PHP-FPM or your preferred service
It is recommended to that your reverse proxy/webserver block access to paths
beginning with .git
, as well as the entirety of the config
directory, as
such paths should never be served to end users. Also, config
should be made
writable by the PHP user if the default SQLite database is used, as it is
stored in config/db.sqlite
. config.php
should be made unwritable for the
PHP user for security.
In Nginx, your configuration might look like this:
upstream files_gallery {
server unix:/run/php-fpm83/files_gallery.sock;
}
map $uri#$arg_name $files_gallery_download_attachment {
~*[^#]*#(?:.*%2F)?(.+) "; filename=$1";
# fallback for if no name param, may break cuz not urlencoded
~(?:[^#/]*/)*([^#]+)#$ "; filename=$1";
default '';
}
server {
### http sockets
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
### tls
ssl_protocols TLSv1.2 TLSv1.3;
# NOTE: prefer server ciphers is set in nginx.conf by default
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
ssl_dhparam /etc/nginx/dh.pem;
ssl_certificate /etc/ssl/files.example.com.fullchain.pem;
ssl_certificate_key /etc/ssl/files.example.com.privkey.pem;
ssl_stapling on;
ssl_stapling_verify on;
# use short-lived IDs instead of tickets for best tradeoff between
# server/client perf and security
# NOTE: shared session cache is set in nginx.conf be default
ssl_session_tickets off;
ssl_session_timeout 2m;
### security
more_set_headers "Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'self' https://files.example.com";
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
more_set_headers "Referrer-Policy: no-referrer, same-origin";
more_set_headers "X-DNS-Prefetch-Control: off";
more_set_headers "X-Robots-Tag: none";
more_clear_headers Server X-Powered-By;
## disable MIME sniffing in IE
more_set_headers "X-Content-Type-Options: nosniff";
## kill chrome tracking garbage just in case
more_set_headers "Permissions-Policy: interest-cohort=(), browsing-topics=(), attribution-reporting=()";
### server config
server_name files.example.com;
root /srv/www/files_gallery;
## tuning upload buffers for http2
# https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
client_body_buffer_size 512k;
## set max upload size and increase upload timeout
client_max_body_size 5G; # NOTE: PHP conf must also be updated
client_body_timeout 2h;
## timeout for choked downloads
send_timeout 10m;
fastcgi_read_timeout 10m;
## more response buffers for better large file perf
fastcgi_buffers 64 8k;
### locations
## Files Gallery and share authentication endpoint
# NOTE: files gallery has internal links pointing to index.php
location ~ ^/(?:(?:index\.php)?$|auth/) {
# your authentication handled here...
auth_request auth.example.com;
auth_request_set $username AUTH_PROVIDER_USERNAME_HEADER;
proxy_add_header REMOTE_USER $username; #auth_user_header from config
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
}
## share links
# NOTE: since share link auth is handled by php, we serve everything
# through it including raw files. the objective is to make it look
# like we are serving the files from the fs directly, even though
# it's all being proxied by the script.
location ~ ^/s/([^/]+) {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
}
## block sensitive paths
location ~ ^/(?:config|_files|\.git|README.md$) {
return 404;
}
## serve user files
location /f/ {
# your authentication handled here...
auth_request auth.example.com;
auth_request_set $username AUTH_PROVIDER_USERNAME_HEADER;
proxy_add_header REMOTE_USER $username; #auth_user_header from config
# your path here...
alias /home/$username/pub/www/;
error_page 404 @show_file_in_ui;
# explore directories in the Files Gallery ui
# NOTE: nginx will redirect to dir/ if it exists and there is no slash,
# so if there is a slash at the end of our URI it's likely a dir
if ($uri ~ ^/(.+)/$) {
return 303 /?$1;
}
# change Content-Disposition based on args
# TODO: should move this to PHP
if ($args ~ ^download) {
more_set_headers "Content-Disposition: attachment$files_gallery_download_attachment";
}
if ($args !~ ^download) {
more_set_headers "Content-Disposition: inline";
}
}
## make 404's go back to ui, looking for the specific file
location @show_file_in_ui {
rewrite ^/f/(.*)/(.*) /?$1#$2 redirect;
rewrite ^/f/(.*) /?#$1 redirect;
}
}