Enable build support by adding .onedev-buildspec.yml
config Loading last commit info...
css
js
schemas
.gitignore
README.md
_filesconfig.php.sample
README.md

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.

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

  1. Copy _filesconfig.php.sample to _filesconfig.php and edit to your liking
  2. Run config/upgrade.sh to install the latest Files Gallery and asset bundle
  3. 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;
    }
}
Please wait...
Page is in error, reload to recover