ShaFi is a self-hosted alternative to WeTransfer. It enables to share files using self expiring tokens. It is similar to youtransfer, but ShaFi implements a backend to allow registered users to have control over their files, to have stats of usage of the sharing links and to create multiple sharing tokens.
ShaFI implements two main workflows:
- Unregistered sharing (similar to WeTransfer): an unregistered user uploads a file to ShaFi, and ShaFi creates an unique token that will automatically expire in 1 week (this value can be configured for your servers).
- Registered sharing: the registered users can upload files, but they are allowed to manage tokens for these files. It is possible to create multiple tokens for the same file, with different features: expiration dates, expiration hits, using password, etc.
- Anonymous uploads can be enabled or not (if disabled, only authenticated users can upload files, but downloads are public).
- It is possible to create multiple access tokens for the same file (thus having differente expiration features such as date or hits).
- It is possible to include a password to protect a specific token.
- It is possible to define a maximum size for the files. And it can be different for anonymous users than for registered ones. Moreover it can be different for each group of registered users (e.g. users, admins, etc.).
- It is possible to stablish a quota for the storage size of files. The quota can be defined per user or per group of users. The quota is not shared, and only the files consume quota (not each token).
- Uploading files is resumable. That means that, if network connection is lost and the file was uploading, the user will need to upload only the part that has not already been uploaded.
You need a working docker installation
- Get ShaFi and build the docker container
$ git clone https://github.com/dealfonso/shafi
$ cd shafi
$ docker build . -t shafidemo
- Start a container with the generated image
$ docker run -id -p 10080:80 --name shafidemo shafidemo
-
Navigate to the URL of the new installation:
http://localhost:10080/shafi
- user: shafi
- password: 123
WARNING: This image should be used with caution, because the passwords are known.
WARNING 2: The files, users and configuration changed in the container will be lost upon stopping the container.
You need a working installation of Apache and PHP 7.2 or higher, and make sure that mod_rewrite
is enabled.
- Prepare a user for the database
$ mysql -u root -p
mysql> create database shafi;
mysql> create user shafi@'localhost' identified by 'a-secret-password';
mysql> grant all privileges on shafi.* to 'shafi'@'localhost';
mysql> flush privileges;
- Get ShaFi and copy it to the apache root folder
$ git clone https://github.com/dealfonso/shafi
$ cp -r shafi /var/www/html
$ chown -R www-data:www-data /var/www/html/shafi
- Prepare the configuration for ShaFi in file
/etc/apache2/conf-available/shafi.conf
(please adapt it to your installation):
<Directory /var/www/html/shafi>
AllowOverride All
</Directory>
- Enable the configuration and restart apache
$ a2enconf shafi
$ a2enmod rewrite
$ service apache2 restart
- Use your browser and navigate to the installation URL and customize your installation:
http://localhost/shafi/install/install.php
In this guide we'll use the following settings:
- Database name: shafi
- Database user: shafi
- Password: a-secret-password
- Web server URL: http://localhost
- URL path for ShaFi: /shafi
Prepare the ShaFi configuration file /var/www/html/shafi/config.php
and the htaccess file /var/www/html/shafi/.htaccess
with the result of the configuration.
The configuration of ShaFi, is controlled by a file config.php
in the root folder of ShaFi. In that file you can include some variable definitions to configure the different options of ShaFi. You have an example file config.php-sample
that contains the different options explained.
It is a good idea to configure ShaFi by using the install.php
web page. It will ask you for the essential configuration and will enable to create the basic configuration file. Then you can follow this guide to fine-tune your installation.
Some of the configuration parameters are explained in the following paragraphs.
It is mandatory to define the parameters to connect ShaFi to the mysql database, using the next variables in config.php
:
// URL of the mysql server
$db_servername = "";
// Database to use for shafi
$db_database = "";
// Username with permissions to manipulate tables in the database
$db_username = "";
// Password for the user
$db_password = "";
Moreover, it is mandatory to set the URL of the server that will host ShaFi, along with the endpoint of the ShaFi application. These values are defined using the following constants:
// Server name (with http:// or https:// prefix)
define('__SERVER_NAME', 'http://localhost');
// Root URL for ShaFi
define('__ROOT_URL', '/');
Files uploaded to ShaFi need a storage folder. It is possible to define the folder in which to store them using the next constant:
define('__STORAGE_BASE_FOLDER', './uploads');
The folder may be an absolute path or a path relative to the ShaFi main folder.
IMPORTANT: The user that runs the web server needs writting permissions in that folder.
The default configuration allows anonymous users to upload files to your ShaFi deployment. But if you do not want to open ShaFi to the world and you want to provide acccess only to your registered users, you can disable anonymous uploads. It can be disabled by including the next line in config.php
:
define('__ANONYMOUS_UPLOAD', false);
The maximum size for the uploaded files is controlled by including the next content in config.php
:
define('__MAX_FILESIZE', array(
'' => 1*1024*1024,
'u' => 10*1024*1024,
'a' => 100*1024*1024
));
Each entry in the __MAX_FILESIZE
array corresponds to a group of users. ''
is for any user (e.g. anonymous), 'u'
is the size for registers users and 'a'
is for admin users.
The maximum storage space that a user can have in ShaFi is controlled by quotas. It is possible to define a quota for each group of users. The quota is controlled by the following array:
define('__STORAGE_QUOTA_GROUP', array(
'u' => 10*1024*1024, // Quota for registered users
'a' => 100*1024*1024 // Quota for admin users
));
IMPORTANT: The values defined here are per user quota, not a shared quota. That means that each user that belongs to a group will be able to use the whole storage space defined by the quota. In the example, each user in 'u'
group will be able to store 10 Mb.
Quota for anonymous users can be controlled using the following constant:
define('__STORAGE_QUOTA_ANONYMOUS', 10 * 1024 * 1024);
NOTE: There is a single anonymous
user, so that quota limits the amount of storage space that can be used by the sum of files of any anonymous user.
The default behaviour of ShaFi is to generate unique ids for the tokens according to this specification. e.g.: http://shafi.myserver.com/9de61b5d-024e-4a8c-8682-a9876ac30aee
These tokens are very long and hard to spell. It is possible to change the way that tokens are generated, by setting the function used to generate them.
ShaFi includes an additional function that you can use. It is called get_random_string
and it will generate short tokens, similar to those generated by goo.gl or bit.ly (e.g. http://shafi.myserver.com/9IjKIN9U). In order to use it, include the following definition in the config.php
file:
define('__TOKEN_GENERATOR_FUNCTION', 'get_random_string');
When an anoymous user uploads a file, ShaFi creates a token, and that token will expire after a period of time or a number of hits. The expiration settings are controlled using the following variable (time is expressed in seconds).
// This example sets the expiration date to one week
define('__ANONYMOUS_UPLOAD_DEFAULT_SECONDS', 60*60*24*7);
// It is a good idea to limit the amount of hits to avoid DoS
define('__ANONYMOUS_UPLOAD_DEFAULT_HITS', 1000);
For registered users, if they do not set expiration conditions, ShaFi will create a default token. The values for the expiration conditions are controlled with the next constants:
define('__DEFAULT_EXPIRATION_SECONDS', 60*60*24*7);
define('__DEFAULT_EXPIRATION_HITS', null);
If any condition is disabled (i.e. set to null), the token will be considered to be infinite. If you do not want to allow infinite tokens in your ShaFi deployment, you can control it with the following constant:
define('__ALLOW_INFINITE_TOKENS', false);
IMPORTANT: If any of the settings is set to null, that condition will be disabled (e.g. limiting the time but not the hits).
In order to create your own groups, you can define a var $__CUSTOM_GROUPS
in config.php
. It is an array of groups, where the index is a single letter that represents the group.
The are a set of legacy groups in ShaFi (defined as next):
define('__LEGACY_PERMISSIONS', [
'l' => 'authorized',
'o' => 'owner',
'u' => 'user',
'a' => 'admin'
])
As an example, if you wanted to define an additional group (e.g. "advanced users"), you could add the next code to config.php
:
$__CUSTOM_GROUPS = [
'A' => 'advanced users`
]
Then you can use the keys of the groups to define other limits (e.g. quotas, maximum sizes, etc.).
In order to use an external authentication mechanism (e.g. Google or any type of SSO), you can create a custom login class and then login into ShaFi.
The authentication using Google is included in SHAFI. It is implemented in the class SHAFI_Op_Google_Auth
, and there is an example file on how to use it in google-auth-login.php
.
To get the Google authentication working, you need to create a project in Google Cloud Console and get the client ID and client secret. Then you need to include them in the
config.php
file, using the definitions__GOOGLE_CLIENT_ID
and__GOOGLE_CLIENT_SECRET
.
It works as follows:
- User is redirected to
/auth-google
web page, which is associated toSHAFI_Op_Google_Auth
. - Method
google_auth
manages the authentication (redirecting to the appropriate login server, etc.). - Once authenticated the return URL must redirect to
/auth-google
again. - Finally, method
_do
gets the authenticated email and tries to login the user (if the e-mail exists).
This class is included in SHAFI, but the implementation is more or less the next one:
class SHAFI_Op_Google_Auth extends SHAFI_Op_Login {
public function google_auth() {
/** things to authenticate using google but not included here */
return [ true, $email ];
}
public function _do() {
global $current_user;
$this->clear_messages();
if ($current_user->is_logged_in())
return $this->add_error_message(__("User is already logged in"));
[ $success, $email ] = $this->google_auth();
if ($email !== null) {
$user = SHAUser::search([ 'email' => $email]);
if (sizeof($user) !== 1) {
session_destroy();
$user = new SHAUser();
return $this->add_error_message(__("User does not exist"));
}
else
return $this->_login($user[0]->get_field('username'));
}
return $this->add_error_message($email);
}
E.g. Rediris SSO is an external authentication identity, and the following class integrates it with ShaFi as an external identity validation. It works as follows:
- User is redirected to
/auth
web page, which is associated toSHAFI_Op_External_Auth
. - Method
authenticate
manages the authentication (redirecting to the appropriate login server, etc.). - Once authenticated, the same
authenticate
redirects to/auth
again. - Finally, method
get_authenticated_user
returns the login username for the authenticated user (ornull
, if none).
The class that implements such behavior is the next:
class SHAFI_Op_External_Auth extends SHAFI_Op_Login {
public function _do() {
global $current_user;
if ($current_user->is_logged_in())
return $this->add_error_message(__("User is already logged in"));
$eauth = new ExternalAuth();
$login = $eauth->get_authenticated_user();
if ($login !== null) {
$user = SHAUser::search([ 'username' => $login]);
if (sizeof($user) !== 1) {
session_destroy();
$user = new SHAUser();
return $this->add_error_message(__("User not found"));
}
else
return $this->_login($login);
}
return $this->add_error_message($eauth->authenticate(add_query_var(['op' => null, 'id' => null], get_rel_url('/auth'))));
}
Method
authenticate
will only return if an error happens.
- The upload of files is made of chunks, by using Resumable.js. So the files are also resumable if they fail to upload.
- The tokens are unique, in the form of http://shafi.com/507564f4-70d2-46ce-ac51-7af9d25dade7
- Diffrent tokens for the same file may have different features (e.g. different expiration dates, different expiration hits, passwords or combinations of them).
- Files are expired but not automatically deleted (so that you can have a track of the files uploaded to your servers).
- Most options are configurable (e.g. default token expiration for both anonymous or registered, allowing passwords for users, grace period for files that are not being shared, etc.)
- It is translated into spanish and english (translations are welcomed).
- It is developed in PHP so it works in most of servers.
- ShaFi is easy to integrate with different authorization mechanisms (e.g. Google)
- ShaFi is designed to use different storage backends. At this time it only implements filesystem backend, but S3 is easy to implement.
- Integrated with Google auth
- Use Amazon S3 as a backend for storing files.
- Allowing multiple files to be uploaded (or downloaded) at once.
- Add quotas of files or storage space.
- Send e-mails when expiration dates are near.
- Hits stats (i.e. integrate with GeoIP and others).