Commit c83a3399 authored by Luc Didry's avatar Luc Didry

Merge branch 'development' into 'master'

Time for a new release

Closes #103, #104, #114, #118, #123, #119, #78, #115, #22, #126, #58, #129, #127 et #128

See merge request !23
parents 7efebff4 412083c7
Pipeline #83809 passed with stages
in 4 minutes and 2 seconds
local/*
files/*
cover_db/*
lufi.conf
lufi.db
*.db
*.db-shm
*.db-wal
*.bak
*.swp
script/hypnotoad.pid
script/*.pid
stop-upload
.zanata-cache/
themes/*
!themes/default
!themes/default/*
image: hatsoftwares/lufi-test-ci:latest
stages:
- podcheck
- carton
- carton_bdd
- tests
before_script:
- rm -f *db
variables:
POSTGRES_DB: lufi_db
POSTGRES_USER: lufi
POSTGRES_PASSWORD: lufi_pwd
MYSQL_DATABASE: lufi_db
MYSQL_USER: lufi
MYSQL_PASSWORD: lufi_pwd
MYSQL_ROOT_PASSWORD: root
### Jobs templates
##
#
.carton_bdd_template: &carton_bdd_definition
stage: carton_bdd
retry: 2
artifacts:
paths:
- local/
expire_in: 1 week
dependencies:
- carton
.sqlite_template: &sqlite_definition
stage: tests
retry: 2
dependencies:
- carton_sqlite
services:
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
coverage: '/Total.* (\d+\.\d+)$/'
.pg_template: &pg_definition
stage: tests
retry: 2
dependencies:
- carton_postgresql
services:
- name: postgres:9.6
alias: postgres
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
coverage: '/Total .*(\d+\.\d+)$/'
.mysql_template: &mysql_definition
stage: tests
retry: 2
dependencies:
- carton_mysql
services:
- name: mariadb:10.1
alias: mariadb
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
coverage: '/Total .*(\d+\.\d+)$/'
### Podcheck
##
#
podcheck:
stage: podcheck
script:
- make podcheck
### Install common dependencies
##
#
carton:
stage: carton
artifacts:
paths:
- local/
expire_in: 1 week
dependencies: []
script:
- carton install --deployment --without=sqlite --without=postgresql --without=mysql
when: always
retry: 2
### Install DB related dependencies
##
#
carton_sqlite:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=postgresql --without=mysql
carton_postgresql:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=sqlite --without=mysql
carton_mysql:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=sqlite --without=postgresql
### SQLite tests
##
#
sqlite:
<<: *sqlite_definition
script:
- MOJO_CONFIG=t/sqlite.conf make test
- MOJO_CONFIG=t/sqlite.conf make cover
### PostgreSQL tests
##
#
postgresql:
<<: *pg_definition
script:
- MOJO_CONFIG=t/postgresql.conf make test
- MOJO_CONFIG=t/postgresql.conf make cover
### MySQL tests
##
#
mysql:
<<: *mysql_definition
script:
- MOJO_CONFIG=t/mysql.conf make test
- MOJO_CONFIG=t/mysql.conf make cover
......@@ -14,3 +14,18 @@
* Yann Le Brech (htpasswd file support)
* Jéssica Da Cunha (portuguese translation)
* Ilker Kulgu (fix IE11 compatibility, Dutch translation)
* Stéphane Baron (bugfix)
* Butterfly of Fire (arabic translation)
* Frju365 (german translation)
## Vulnerabilities / bug hunters
Lufi participated to a [Hackpéro](https://hackpero.com/) (sort of a bug bounty hackathon), thanks to [Bounty factory](https://hackpero.com/).
Many thanks to those who found bugs and vulnerabilities:
* joker2a
* March
* Nicknam3
* SaxX
* tfairane
Revision history for Lufi
0.03 2018-??-??
- Use Mojo::SQLite instead of ORLite
- Use FiatTux plugins
- Option to force "Burn after reading" for each uploaded file
- Use GzipStatic and StaticCache plugins for speed
- Allow to block files by setting an abuse field in DB
- Display file size when uploading
- Add Content-Security-Policy header
- Update sjcl.js
- Mitigate genRandomKey exception risk
- Add report file link in the navbar
- Allow to choose your language
- Use a recurrent task to provision shorts
- Add a command to migrate data from SQLite to an other database
- Add a test suite
- MySQL support
- Display max size on upload page
- Add CSRF token challenge on login
- Add CSRF token challenge on logout
- Add constraints on mail sending to prevent spam sending (not perfect, but
should be good enough)
- Add Code of Conduct
- Add arabic translation
- Add german translation
0.02.2 2017-09-18
- Fix cron tasks bug
......
EXTRACTDIR=-D lib -D themes/default/templates
EN=themes/default/lib/Lufi/I18N/en.po
FR=themes/default/lib/Lufi/I18N/fr.po
IT=themes/default/lib/Lufi/I18N/it.po
OC=themes/default/lib/Lufi/I18N/oc.po
CA=themes/default/lib/Lufi/I18N/ca.po
PT=themes/default/lib/Lufi/I18N/pt.po
NL=themes/default/lib/Lufi/I18N/nl.po
XGETTEXT=carton exec local/bin/xgettext.pl
POT=themes/default/lib/Lufi/I18N/lufi.pot
XGETTEXT=carton exec local/bin/xgettext.pl -u
CARTON=carton exec
REAL_LUFI=script/application
LUFI=script/lufi
locales:
$(XGETTEXT) $(EXTRACTDIR) -o $(EN) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(FR) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(IT) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(OC) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(CA) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(PT) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(NL) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(POT) 2>/dev/null
push-locales: locales
zanata-cli -q -B push --errors --project-version `git branch | grep \* | cut -d ' ' -f2-`
pull-locales:
zanata-cli -q -B pull --min-doc-percent 50 --project-version `git branch | grep \* | cut -d ' ' -f2-`
stats-locales:
zanata-cli -q stats --project-version `git branch | grep \* | cut -d ' ' -f2-`
podcheck:
podchecker lib/Lufi/DB/File.pm lib/Lufi/DB/Slice.pm
cover:
PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) cover --ignore_re '^local'
test:
$(CARTON) $(REAL_LUFI) test
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) prove -l -f -o t/test.t
clean:
rm -rf lufi.db files/
dev: clean
$(CARTON) morbo $(LUFI) --listen http://0.0.0.0:3000 --watch lib/ --watch script/ --watch themes/ --watch lufi.conf
......
......@@ -2,7 +2,7 @@
## What does Lufi mean?
Lufi means Let's Upload that FIle.
Lufi means Let's Upload that FIle. It's a E2E encrypted file sharing software.
## Which browsers are compatible?
......@@ -62,9 +62,9 @@ There is the web interface, but you can use a CLI client too! Have a look at <ht
## Internationalization
Lufi comes with English and French languages. It will choose the language to display from the browser's settings.
Lufi comes with several languages.
If you want to add a language, merge-requests are welcome.
Please, see [this wiki page](https://framagit.org/fiat-tux/hat-softwares/lufi/wikis/contribute#internationalization) to know how to contribute to internationalization.
## Authors
......@@ -76,6 +76,8 @@ Please consider contributing, either by [reporting issues](https://framagit.org/
The details on how to contribute are on the [wiki](https://framagit.org/luc/lufi/wikis/contribute).
This software uses [Fiat Tux Code of conduct](https://framagit.org/fiat-tux/code-of-conduct/blob/master/README.md).
## Make a donation
You can make a donation to the author on [Tipeee](https://www.tipeee.com/fiat-tux) or on [Liberapay](https://liberapay.com/sky/).
......
requires 'inc::Module::Install';
requires 'Mojolicious', '>= 7.31';
requires 'ORLite';
requires 'Mojolicious::Plugin::DebugDumperHelper';
requires 'Mojolicious::Plugin::PgURLHelper';
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::Mail';
requires 'Mojolicious::Plugin::Authentication';
requires 'Mojo::Pg';
requires 'EV';
requires 'Mojolicious::Plugin::GzipStatic';
requires 'Mojolicious::Plugin::StaticCache';
requires 'Mojolicious::Plugin::CSPHeader';
requires 'Mojolicious::Plugin::FiatTux::Helpers', '== 0.08', url => 'https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-fiattux-helpers/-/archive/0.08/mojolicious-plugin-fiattux-helpers-0.08.tar.gz';
requires 'Mojolicious::Plugin::FiatTux::GrantAccess', '== 0.05', url => 'https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-fiattux-grantaccess/-/archive/0.05/mojolicious-plugin-fiattux-grantaccess-0.05.tar.gz';
requires 'Mojolicious::Plugin::FiatTux::Themes', '== 0.02', url => 'https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-fiattux-themes/-/archive/0.02/mojolicious-plugin-fiattux-themes-0.02.tar.gz';
requires 'Filesys::DiskUsage';
requires 'Switch';
requires 'Locale::Maketext';
......@@ -14,8 +16,40 @@ requires 'Locale::Maketext::Extract';
requires 'Email::Valid';
requires 'Number::Bytes::Human';
requires 'Filesys::DfPortable';
requires 'Switch';
requires 'Data::Entropy';
requires 'Net::LDAP';
requires 'Crypt::SaltedHash';
requires 'Apache::Htpasswd';
requires 'Data::Validate::URI';
requires 'Term::ProgressBar';
requires 'URI::Find';
# Mojolicious optional deps
feature 'optional_deps' => sub {
requires 'Cpanel::JSON::XS';
requires 'EV';
requires 'IO::Socket::Socks';
requires 'Role::Tiny';
};
feature 'test' => sub {
requires 'Devel::Cover';
};
feature 'ldap', 'LDAP authentication support' => sub {
requires 'Net::LDAP';
requires 'Mojolicious::Plugin::Authentication';
};
feature 'htpasswd', 'Htpasswd authentication support' => sub {
requires 'Apache::Htpasswd';
requires 'Mojolicious::Plugin::Authentication';
};
feature 'postgresql', 'PostgreSQL support' => sub {
requires 'Mojo::Pg';
requires 'Mojolicious::Plugin::PgURLHelper';
};
feature 'sqlite', 'SQLite support' => sub {
requires 'Mojo::SQLite', '>= 3.000';
};
feature 'mysql', 'MySQL support' => sub {
requires 'DBD::mysql', '== 4.046';
requires 'Mojo::mysql';
requires 'Mojolicious::Plugin::PgURLHelper';
};
This diff is collapsed.
This diff is collapsed.
......@@ -3,6 +3,7 @@ package Lufi::Command::cron::cleanbdd;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Delete IP addresses from database after configured delay.';
has usage => sub { shift->extract_usage };
......@@ -19,10 +20,7 @@ sub run {
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => {
dbtype => 'sqlite',
keep_ip_during => 365,
}
default => $default_config
});
my $separation = time() - $config->{keep_ip_during} * 86400;
......
......@@ -3,6 +3,7 @@ package Lufi::Command::cron::cleanfiles;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Delete expired files.';
has usage => sub { shift->extract_usage };
......@@ -19,10 +20,7 @@ sub run {
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => {
dbtype => 'sqlite',
upload_dir => 'files',
}
default => $default_config
});
my $time = time();
......
......@@ -4,6 +4,7 @@ use Filesys::DiskUsage qw/du/;
use Lufi::DB::File;
use Switch;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Watch the files directory and take action when over quota';
has usage => sub { shift->extract_usage };
......@@ -20,11 +21,7 @@ sub run {
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => {
dbtype => 'sqlite',
upload_dir => 'files',
policy_when_full => 'warn'
}
default => $default_config
});
if (defined($config->{max_total_size})) {
......
package Lufi::Command::sqliteToOtherDB;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use Lufi::DB::Slice;
use Mojo::SQLite;
use FindBin qw($Bin);
use Term::ProgressBar;
use Lufi::DefaultConfig qw($default_config);
has description => 'Migrate the records from a SQLite db to the currently configured database';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
if ($config->{dbtype} eq 'sqlite') {
say 'Please configure `dbtype` to something else than `sqlite` to use this command.';
print $c->usage;
exit 1;
}
my $sqlite = Mojo::SQLite->new('sqlite:'.$config->{db_path});
my $files = $sqlite->db->select('files', undef)->hashes;
my $slices = $sqlite->db->select('slices', undef)->hashes;
my $progress = Term::ProgressBar->new({count => $files->size + $slices->size});
$files->each(sub {
my ($file, $num) = @_;
$progress->update();
Lufi::DB::File->new(app => $c->app)
->short($file->{short})
->deleted($file->{deleted})
->mediatype($file->{mediatype})
->filename($file->{filename})
->filesize($file->{filesize})
->counter($file->{counter})
->delete_at_first_view($file->{delete_at_first_view})
->delete_at_day($file->{delete_at_day})
->created_at($file->{created_at})
->created_by($file->{created_by})
->last_access_at($file->{last_access_at})
->mod_token($file->{mod_token})
->nbslices($file->{nbslices})
->complete($file->{complete})
->passwd($file->{passwd})
->abuse($file->{abuse})
->write();
});
$slices->each(sub {
my ($slice, $num) = @_;
Lufi::DB::Slice->new(app => $c->app)
->short($slice->{short})
->j($slice->{j})
->path($slice->{path})
->write();
$progress->update();
});
}
=encoding utf8
=head1 NAME
Lufi::Command::cron::sqliteToOtherDB Migrate the records from a SQLite db to the currently configured database
=head1 SYNOPSIS
Usage: script/lufi sqliteToOtherDB
This command needs you to:
- set `db_path` in your config file (otherwise, it will use the default path, `lufi.db` to migrate data from)
- set `dbtype` to an other database type in your config file
- configure the other database access in your config file
=cut
1;
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Auth;
use Mojo::Base 'Mojolicious::Controller';
sub login_page {
my $c = shift;
if ($c->is_user_authenticated) {
$c->redirect_to('index');
} else {
$c->render(template => 'login');
}
}
sub login {
my $c = shift;
my $login = $c->param('login');
my $pwd = $c->param('password');
if ($c->validation->csrf_protect->has_error('csrf_token')) {
$c->stash(msg => $c->l('Bad CSRF token.'));
$c->render(template => 'login');
} else {
if($c->authenticate($login, $pwd)) {
$c->redirect_to('index');
} else {
$c->stash(msg => $c->l('Please, check your credentials or your right to access this service: unable to authenticate.'));
$c->render(template => 'login');
}
}
}
sub log_out {
my $c = shift;
if ($c->is_user_authenticated) {
if ($c->validation->csrf_protect->has_error('csrf_token')) {
$c->stash(msg => $c->l('Bad CSRF token.'));
} else {
$c->logout;
}
}
$c->render(template => 'logout');
}
1;
......@@ -11,6 +11,16 @@ use Number::Bytes::Human qw(format_bytes);
use Filesys::DfPortable;
use Crypt::SaltedHash;
sub files {
my $c = shift;
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd'))) || $c->is_user_authenticated) {
$c->render(template => 'files');
} else {
$c->redirect_to('login');
}
}
sub upload {
my $c = shift;
......@@ -111,9 +121,11 @@ sub upload {
if (defined($c->config('ldap')) || defined($c->config('htpasswd'))) {
$creator = 'User: '.$c->current_user.', IP: '.$creator;
}
my $delete_at_first_view = ($json->{del_at_first_view}) ? 1 : 0;
$delete_at_first_view = 1 if $c->app->config('force_burn_after_reading');
$f = Lufi::DB::File->new(app => $c->app)->get_empty()
->created_by($creator)
->delete_at_first_view(($json->{del_at_first_view}) ? 1 : 0)
->delete_at_first_view($delete_at_first_view)
->delete_at_day($delay)
->mediatype($json->{type})
->filename($json->{name})
......@@ -153,8 +165,6 @@ sub upload {
}
}
$c->provisioning;
$ws->send(to_json(
{
success => true,
......@@ -190,6 +200,13 @@ sub upload {
$c->app->log->debug('Client disconnected');
}
);
} else {
$c->on(
message => sub {
$c->app->log->info(sprintf('Someone unauthenticated tried to upload a file. IP: %s', $c->ip));
$c->finish;
}
);
}
}
......@@ -227,6 +244,20 @@ sub download {
)));
}
);
} elsif (defined($ldfile->abuse)) {
my $abuse_msg = $c->l('This file has been deactivated by the admins. Contact them to know why.');
$abuse_msg = $c->app->config('abuse')->{$ldfile->abuse} if ($c->app->config('abuse') && $c->app->config('abuse')->{$ldfile->abuse});
$c->on(
message => sub {
my ($ws, $json) = @_;
$c->send(decode('UTF-8', encode_json(
{
success => false,
msg => $abuse_msg
}
)));
}
);
} elsif ($ldfile->complete) {
my $f = $ldfile;
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Mail;
use Mojo::Base 'Mojolicious::Controller';
use Email::Valid;
use Mojo::JSON qw(decode_json);
use Mojo::URL;
use Email::Valid;
use URI::Find;
sub render_mail {
my $c = shift;
my $links = (defined($c->param('links'))) ? decode_json($c->param('links')) : [];
$c->redirect_to('/') unless (scalar(@{$links}));
$c->render(
template => 'mail',
links => $links
......@@ -20,7 +24,34 @@ sub send_mail {
my $validation = $c->validation;
return $c->render(text => $c->l('Bad CSRF token!'), status => 403) if $validation->csrf_protect->has_error('csrf_token');
my $emails = $c->param('emails');
my $emails = $c->param('emails');
my $body = $c->param('body');
my $subject = $c->param('subject');
my $msg = '';
my $base_url = $c->req->url->to_abs->path('/r/');
my $fixed_url = $base_url;
if ($c->config('fixed_domain')) {
$fixed_url->host($c->config('fixed_domain'));
}
my $at_least_one_instance_url = 0;
my $finder = URI::Find->new(sub {
my ($uri, $orig_uri) = @_;
$uri = Mojo::URL->new($uri);
if ($uri->host ne $base_url->to_abs->host && $uri->host ne $fixed_url->to_abs->host) {
$msg .= $c->l('You can\'t add URLs that are not related to this instance.').'<br>';
} elsif (index($orig_uri, $fixed_url->to_abs->to_string) > -1) {
$at_least_one_instance_url = 1;
}
return $orig_uri;
});
$finder->find(\$body);
$finder->find(\$subject);
$c->debug($at_least_one_instance_url);
unless ($at_least_one_instance_url) {
$msg .= $c->l('The body of the mail must contain at least one URL pointing to a file hosted on this instance.').'<br>';
}
$emails =~ s/ //g;
my @a = split(',', $emails);
......@@ -33,23 +64,23 @@ sub send_mail {
}
}
my $msg = '';
if (scalar(@bad)) {
$msg .= $c->l('The following email addresses are not valid: %1', join(', ', @bad))."\n";
$msg .= $c->l('The following email addresses are not valid: %1', join(', ', @bad)).'<br>';
}
$msg .= $c->l('You must give email addresses.')."\n" unless (scalar(@a));
$msg .= $c->l('The email subject can\'t be empty.')."\n" unless ($c->param('subject'));
$msg .= $c->l('The email body can\'t be empty.')."\n" unless ($c->param('body'));
$msg .= $c->l('You must give email addresses.').'<br>' unless (scalar(@a));
$msg .= $c->l('The email subject can\'t be empty.').'<br>' unless ($subject);
$msg .= $c->l('The email body can\'t be empty.').'<br>' unless ($body);
if ($msg) {
return $c->render(
template => 'mail',
msg => $msg,
links => [],
values => {
emails => $emails,
subject => $c->param('subject'),
body => $c->param('body')
subject => $subject,
body => $body
}
)
}
......@@ -57,8 +88,8 @@ sub send_mail {
$c->mail(
from => $c->config('mail_sender'),
bcc => $emails,
subject => $c->param('subject'),
data => $c->param('body')
subject => $subject,
data => $body
);