From e41b08601d513e9a2ca99685c3adb86f4c029380 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Oct 24 2018 19:56:37 +0000 Subject: Add a test suite --- diff --git a/.gitignore b/.gitignore index 489a46d..6519248 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ local/* files/* +cover_db/* lufi.conf *.db *.db-shm diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1e705af --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,126 @@ +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 + when: always + retry: 2 + +### Install DB related dependencies +## +# +carton_sqlite: + <<: *carton_bdd_definition + script: + - carton install --deployment --without=postgresql +carton_postgresql: + <<: *carton_bdd_definition + script: + - carton install --deployment --without=sqlite +#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 diff --git a/CHANGELOG b/CHANGELOG index 4362e32..8b12fc2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ Revision history for Lufi - 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 0.02.2 2017-09-18 - Fix cron tasks bug diff --git a/Makefile b/Makefile index 8b75700..b239aab 100644 --- a/Makefile +++ b/Makefile @@ -23,11 +23,15 @@ locales: 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) prove -l -f -o t/basic.t + @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 diff --git a/cpanfile b/cpanfile index d797c75..22d3594 100644 --- a/cpanfile +++ b/cpanfile @@ -21,6 +21,9 @@ requires 'Crypt::SaltedHash'; requires 'Data::Validate::URI'; requires 'Term::ProgressBar'; +feature 'test' => sub { + requires 'Devel::Cover'; +}; feature 'ldap', 'LDAP authentication support' => sub { requires 'Net::LDAP'; requires 'Mojolicious::Plugin::Authentication'; diff --git a/cpanfile.snapshot b/cpanfile.snapshot index d5f77c7..f10c591 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -323,6 +323,65 @@ DISTRIBUTIONS Data::Validate::Domain 0 Data::Validate::IP 0 ExtUtils::MakeMaker 0 + Devel-Cover-1.31 + pathname: P/PJ/PJCJ/Devel-Cover-1.31.tar.gz + provides: + Devel::Cover 1.31 + Devel::Cover::Annotation::Git 1.31 + Devel::Cover::Annotation::Random 1.31 + Devel::Cover::Annotation::Svk 1.31 + Devel::Cover::Branch 1.31 + Devel::Cover::Collection 1.31 + Devel::Cover::Collection::Template::Provider 1.31 + Devel::Cover::Condition 1.31 + Devel::Cover::Condition_and_2 1.31 + Devel::Cover::Condition_and_3 1.31 + Devel::Cover::Condition_or_2 1.31 + Devel::Cover::Condition_or_3 1.31 + Devel::Cover::Condition_xor_4 1.31 + Devel::Cover::Criterion 1.31 + Devel::Cover::DB 1.31 + Devel::Cover::DB::Criterion 1.31 + Devel::Cover::DB::Digests 1.31 + Devel::Cover::DB::File 1.31 + Devel::Cover::DB::IO 1.31 + Devel::Cover::DB::IO::Base 1.31 + Devel::Cover::DB::IO::JSON 1.31 + Devel::Cover::DB::IO::Sereal 1.31 + Devel::Cover::DB::IO::Storable 1.31 + Devel::Cover::DB::Run 1.31 + Devel::Cover::DB::Structure 1.31 + Devel::Cover::Html_Common 1.31 + Devel::Cover::Op 1.31 + Devel::Cover::Pod 1.31 + Devel::Cover::Report::Compilation 1.31 + Devel::Cover::Report::Html 1.31 + Devel::Cover::Report::Html_basic 1.31 + Devel::Cover::Report::Html_basic::Template::Provider 1.31 + Devel::Cover::Report::Html_minimal 1.31 + Devel::Cover::Report::Html_subtle 1.31 + Devel::Cover::Report::Html_subtle::Template::Provider 1.31 + Devel::Cover::Report::Json 1.31 + Devel::Cover::Report::Sort 1.31 + Devel::Cover::Report::Text 1.31 + Devel::Cover::Report::Text2 1.31 + Devel::Cover::Report::Vim 1.31 + Devel::Cover::Report::Vim::Template::Provider 1.31 + Devel::Cover::Statement 1.31 + Devel::Cover::Subroutine 1.31 + Devel::Cover::Test 1.31 + Devel::Cover::Time 1.31 + Devel::Cover::Truth_Table 1.31 + Devel::Cover::Truth_Table::Row 1.31 + Devel::Cover::Util 1.31 + Devel::Cover::Web 1.31 + requirements: + B::Debug 0 + Digest::MD5 0 + ExtUtils::MakeMaker 0 + HTML::Entities 3.69 + Storable 0 + Test::More 0 Devel-GlobalDestruction-0.14 pathname: H/HA/HAARG/Devel-GlobalDestruction-0.14.tar.gz provides: diff --git a/lib/Lufi.pm b/lib/Lufi.pm index ed33bd4..2e41533 100644 --- a/lib/Lufi.pm +++ b/lib/Lufi.pm @@ -143,7 +143,7 @@ sub startup { # Logout page $r->get('/logout') - ->to('Auth#logout') + ->to('Auth#log_out') ->name('logout'); } diff --git a/lib/Lufi/Controller/Auth.pm b/lib/Lufi/Controller/Auth.pm index 8d2a50e..77401c9 100644 --- a/lib/Lufi/Controller/Auth.pm +++ b/lib/Lufi/Controller/Auth.pm @@ -26,7 +26,7 @@ sub login { } } -sub logout { +sub log_out { my $c = shift; if ($c->is_user_authenticated) { diff --git a/lib/Lufi/Controller/Files.pm b/lib/Lufi/Controller/Files.pm index d45ad17..bb72521 100644 --- a/lib/Lufi/Controller/Files.pm +++ b/lib/Lufi/Controller/Files.pm @@ -200,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; + } + ); } } diff --git a/lib/Lufi/DB/File.pm b/lib/Lufi/DB/File.pm index b0fd6ae..eababcd 100644 --- a/lib/Lufi/DB/File.pm +++ b/lib/Lufi/DB/File.pm @@ -431,6 +431,28 @@ sub delete_creator_before { $c->app->dbi->db->query('UPDATE files SET created_by = NULL WHERE created_by IS NOT NULL AND created_at < ?', $separation); } +=head2 delete_all + +=over 1 + +=item B : C<$c-Edelete_all()> + +=item B : none + +=item B : delete all file records from database unconditionnally + +=item B : nothing + +=back + +=cut + +sub delete_all { + my $c = shift; + + $c->app->dbi->db->delete('files'); +} + =head2 _slurp =over 1 diff --git a/lib/Lufi/DB/Slice.pm b/lib/Lufi/DB/Slice.pm index cf3d136..648eecb 100644 --- a/lib/Lufi/DB/Slice.pm +++ b/lib/Lufi/DB/Slice.pm @@ -136,6 +136,28 @@ sub get_slices_of_file { return c(@slices); } +=head2 delete_all + +=over 1 + +=item B : C<$c-Edelete_all()> + +=item B : none + +=item B : delete all file records from database unconditionnally + +=item B : nothing + +=back + +=cut + +sub delete_all { + my $c = shift; + + $c->app->dbi->db->delete('slices'); +} + =head2 _slurp =over 1 diff --git a/t/basic.t b/t/basic.t deleted file mode 100644 index d04e686..0000000 --- a/t/basic.t +++ /dev/null @@ -1,9 +0,0 @@ -use Mojo::Base -strict; - -use Test::More; -use Test::Mojo; - -my $t = Test::Mojo->new('Lufi'); -$t->get_ok('/')->status_is(200)->content_like(qr/Lufi/i); - -done_testing(); diff --git a/t/lufi.passwd b/t/lufi.passwd new file mode 100644 index 0000000..c893b45 --- /dev/null +++ b/t/lufi.passwd @@ -0,0 +1 @@ +luc:$apr1$zG4UAKGa$FqSi4widrkVH/pT3qPawd. diff --git a/t/postgresql.conf b/t/postgresql.conf new file mode 100644 index 0000000..282333b --- /dev/null +++ b/t/postgresql.conf @@ -0,0 +1,233 @@ +# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: +{ + #################### + # Hypnotoad settings + #################### + # see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings + hypnotoad => { + # array of IP addresses and ports you want to listen to + listen => ['http://127.0.0.1:8081'], + # if you use Lufi behind a reverse proxy like Nginx, you want to set proxy to 1 + # if you use Lufi directly, let it commented + #proxy => 1, + + # Please read http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers + # to adjust this to your server + workers => 30, + clients => 1, + }, + + # put a way to contact you here and uncomment it + # you can put some HTML in it + # MANDATORY + contact => 'Contact page', + + # put an URL or an email address to receive file reports and uncomment it + # it's for make reporting illegal files easy for users + # MANDATORY + report => 'report@example.com', + + # array of random strings used to encrypt cookies + # optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT + #secrets => ['fdjsofjoihrei'], + + # choose a theme. See the available themes in `themes` directory + # optional, default is 'default' + #theme => 'default', + + # length of the random URL + # optional, default is 8 + #length => 8, + + # how many URLs will be provisioned in a batch ? + # optional, default is 5 + #provis_step => 5, + + # max number of URLs to be provisioned + # optional, default is 100 + #provisioning => 100, + + # length of the modify/delete token + # optional, default is 32 + #token_length => 32, + + # max file size, in octets + # you can write it 100*1024*1024 + # optional, no default + #max_file_size => 104857600, + + # if you want to have piwik statistics, provide a piwik image tracker + # only the image tracker is allowed, no javascript + # optional, no default + #piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&rec=1', + + # broadcast_message which will displayed on the index page + # optional, no default + #broadcast_message => 'Maintenance', + + # default time limit for files + # valid values are 0, 1, 7, 30 and 365 + # optional, default is 0 (no limit) + #default_delay => 0, + + # number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay) + # a warning message will be displayed on homepage + # optional, default is 0 (no limit) + #max_delay => 0, + + # size thresholds: if you want to define max delays for different sizes of file + # the keys are size in Bytes, you can't have 10*1000*10000 as key + # if a file is smaller than the smallest configured size, it will have a expiration delay of max_delay (see above) + # optional, default is using max_delay (see above) for all sizes + #delay_for_size => { + # 10000000 => 90, # between 10MB and 50MB => max is 90 days, less than 10MB => max is max_delay (see above) + # 50000000 => 60, # between 50MB ans 1GB => max is 60 days + # 1000000000 => 2, # more than 1GB => max is 2 days + #}, + + # URL sub-directory in which you want Lufi to be accessible + # example: you want to have Lufi under https://example.org/lufi/ + # => set prefix to '/lufi' or to '/lufi/', it doesn't matter + # optional, defaut is / + #prefix => '/', + + # array of authorized domains for API calls. + # if you want to authorize everyone to use the API: ['*'] + # optional, no domains allowed by default + #allowed_domains => ['http://1.example.com', 'http://2.example.com'], + + # if set, the shortened URLs will use this domain + # optional + #fixed_domain => 'example.org', + + # Mail configuration + # See https://metacpan.org/pod/Mojolicious::Plugin::Mail#EXAMPLES + # Optional, default to sendmail method with no arguments + #mail => { + # # Valid values are 'sendmail' and 'smtp' + # how => 'smtp', + # howargs => ['smtp.example.org'] + #}, + + # Email sender address + # Optional, default to no-reply@lufi.io + #mail_sender => 'no-reply@lufi.io', + + # choose what database you want to use + # valid choices are sqlite and postgresql (all lowercase) + # optional, default is sqlite + dbtype => 'postgresql', + + # SQLite ONLY - only used if dbtype is set to sqlite + # define a path to the SQLite database + # you can define it relative to lufi directory or set an absolute path + # remember that it has to be in a directory writable by Lufi user + # optional, default is lufi.db + #db_path => 'lufi.db', + + # PostgreSQL ONLY - only used if dbtype is set to postgresql + # these are the credentials to access the PostgreSQL database + # mandatory if you choosed postgresql as dbtype + pgdb => { + database => 'lufi_db', + host => 'postgres', + user => 'lufi', + pwd => 'lufi_pwd' + }, + + # define a path to the upload directory, where the uploaded files will be stored + # you can define it relative to lufi directory or set an absolute path + # remember that it has to be in a directory writable by Lufi user + # DO NOT CHANGE THIS IF FILES HAVE BEEN ALREADY UPLOADED: THEY WILL NOT BE DOWNLOADABLE ANYMORE + # optional, default is 'files' + #upload_dir => 'files', + + # set `ldap` if you want that only authenticated users can upload files + # please note that everybody can still download files + # optional, no default + #ldap => { uri => 'ldap://rroemhild-test-openldap', user_tree => 'ou=people,dc=planetexpress,dc=com', bind_dn => 'cn=admin,dc=planetexpress,dc=com', bind_pwd => 'GoodNewsEveryone', user_attr => 'uid', user_filter => '' }, + + # set `htpasswd` if you want to use an htpasswd file instead of ldap + # see 'man htpasswd' to know how to create such file + #htpasswd => 't/lstu.passwd', + + # if you've set ldap above, the session will last `session_duration` seconds before + # the user needs to reauthenticate + # optional, default is 3600 + #session_duration => 3600, + + # allow to add a password on files, asked before allowing to download files + # optional, default is 0 + allow_pwd_on_files => 1, + + # force all files to be in "Burn after reading mode" + # optional, default is 0 + #force_burn_after_reading => 0, + + # if set, the files' URLs will always use this domain + # optional, no default + #fixed_domain => 'example.org', + + # abuse reasons + # set an integer in the abuse field of a file in the database and it will not be downloadable anymore + # the reason will be displayed to the downloader, according to the reasons you will configure here. + # optional, no default + abuse => { + 0 => 'Copyright infringment', + 1 => 'Illegal content', + }, + + # Content-Security-Policy header that will be sent by Lufi + # Set to '' to disable CSP header + # https://content-security-policy.com/ provides a good documentation about CSP. + # https://report-uri.com/home/generate provides a tool to generate a CSP header. + # optional, default is "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" + # the default value is good for `default` and `milligram` themes + #csp => "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" + + # X-Frame-Options header that will be sent by Lufi + # Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/' + # Set to '' to disable X-Frame-Options header + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + # Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly + # to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) + # optional, default is 'DENY' + #x_frame_options => 'DENY', + + # X-Content-Type-Options that will be sent by Lufi + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + # Set to '' to disable X-Content-Type-Options header + # optional, default is 'nosniff' + #x_content_type_options => 'nosniff', + + # X-XSS-Protection that will be sent by Lufi + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection + # Set to '' to disable X-XSS-Protection header + # optional, default is '1; mode=block' + #x_xss_protection => '1; mode=block', + + ######################### + # Lufi cron jobs settings + ######################### + + # number of days senders' IP addresses are kept in database + # after that delay, they will be deleted from database (used with script/lufi cron cleanbdd) + # optional, default is 365 + #keep_ip_during => 365, + + # max size of the files directory, in octets + # used by script/lufi cron watch to trigger an action + # optional, no default + #max_total_size => 10*1024*1024*1024, + + # default action when files directory is over max_total_size (used with script/lufi cron watch) + # valid values are 'warn', 'stop-upload' and 'delete' + # please, see readme + # optional, default is 'warn' + #policy_when_full => 'warn', + + # images which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task + # if delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted + # optional, no default + #delete_no_longer_viewed_files => 90, +}; diff --git a/t/sqlite.conf b/t/sqlite.conf new file mode 100644 index 0000000..88d8787 --- /dev/null +++ b/t/sqlite.conf @@ -0,0 +1,233 @@ +# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: +{ + #################### + # Hypnotoad settings + #################### + # see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings + hypnotoad => { + # array of IP addresses and ports you want to listen to + listen => ['http://127.0.0.1:8081'], + # if you use Lufi behind a reverse proxy like Nginx, you want to set proxy to 1 + # if you use Lufi directly, let it commented + #proxy => 1, + + # Please read http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers + # to adjust this to your server + workers => 30, + clients => 1, + }, + + # put a way to contact you here and uncomment it + # you can put some HTML in it + # MANDATORY + contact => 'Contact page', + + # put an URL or an email address to receive file reports and uncomment it + # it's for make reporting illegal files easy for users + # MANDATORY + report => 'report@example.com', + + # array of random strings used to encrypt cookies + # optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT + #secrets => ['fdjsofjoihrei'], + + # choose a theme. See the available themes in `themes` directory + # optional, default is 'default' + #theme => 'default', + + # length of the random URL + # optional, default is 8 + #length => 8, + + # how many URLs will be provisioned in a batch ? + # optional, default is 5 + #provis_step => 5, + + # max number of URLs to be provisioned + # optional, default is 100 + #provisioning => 100, + + # length of the modify/delete token + # optional, default is 32 + #token_length => 32, + + # max file size, in octets + # you can write it 100*1024*1024 + # optional, no default + #max_file_size => 104857600, + + # if you want to have piwik statistics, provide a piwik image tracker + # only the image tracker is allowed, no javascript + # optional, no default + #piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&rec=1', + + # broadcast_message which will displayed on the index page + # optional, no default + #broadcast_message => 'Maintenance', + + # default time limit for files + # valid values are 0, 1, 7, 30 and 365 + # optional, default is 0 (no limit) + #default_delay => 0, + + # number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay) + # a warning message will be displayed on homepage + # optional, default is 0 (no limit) + #max_delay => 0, + + # size thresholds: if you want to define max delays for different sizes of file + # the keys are size in Bytes, you can't have 10*1000*10000 as key + # if a file is smaller than the smallest configured size, it will have a expiration delay of max_delay (see above) + # optional, default is using max_delay (see above) for all sizes + #delay_for_size => { + # 10000000 => 90, # between 10MB and 50MB => max is 90 days, less than 10MB => max is max_delay (see above) + # 50000000 => 60, # between 50MB ans 1GB => max is 60 days + # 1000000000 => 2, # more than 1GB => max is 2 days + #}, + + # URL sub-directory in which you want Lufi to be accessible + # example: you want to have Lufi under https://example.org/lufi/ + # => set prefix to '/lufi' or to '/lufi/', it doesn't matter + # optional, defaut is / + #prefix => '/', + + # array of authorized domains for API calls. + # if you want to authorize everyone to use the API: ['*'] + # optional, no domains allowed by default + #allowed_domains => ['http://1.example.com', 'http://2.example.com'], + + # if set, the shortened URLs will use this domain + # optional + #fixed_domain => 'example.org', + + # Mail configuration + # See https://metacpan.org/pod/Mojolicious::Plugin::Mail#EXAMPLES + # Optional, default to sendmail method with no arguments + #mail => { + # # Valid values are 'sendmail' and 'smtp' + # how => 'smtp', + # howargs => ['smtp.example.org'] + #}, + + # Email sender address + # Optional, default to no-reply@lufi.io + #mail_sender => 'no-reply@lufi.io', + + # choose what database you want to use + # valid choices are sqlite and postgresql (all lowercase) + # optional, default is sqlite + #dbtype => 'sqlite', + + # SQLite ONLY - only used if dbtype is set to sqlite + # define a path to the SQLite database + # you can define it relative to lufi directory or set an absolute path + # remember that it has to be in a directory writable by Lufi user + # optional, default is lufi.db + db_path => 'sqlite.db', + + # PostgreSQL ONLY - only used if dbtype is set to postgresql + # these are the credentials to access the PostgreSQL database + # mandatory if you choosed postgresql as dbtype + #pgdb => { + # database => 'lufi', + # host => 'localhost', + # #user => 'DBUSER', + # #pwd => 'DBPASSWORD' + #}, + + # define a path to the upload directory, where the uploaded files will be stored + # you can define it relative to lufi directory or set an absolute path + # remember that it has to be in a directory writable by Lufi user + # DO NOT CHANGE THIS IF FILES HAVE BEEN ALREADY UPLOADED: THEY WILL NOT BE DOWNLOADABLE ANYMORE + # optional, default is 'files' + #upload_dir => 'files', + + # set `ldap` if you want that only authenticated users can upload files + # please note that everybody can still download files + # optional, no default + #ldap => { uri => 'ldap://rroemhild-test-openldap', user_tree => 'ou=people,dc=planetexpress,dc=com', bind_dn => 'cn=admin,dc=planetexpress,dc=com', bind_pwd => 'GoodNewsEveryone', user_attr => 'uid', user_filter => '' }, + + # set `htpasswd` if you want to use an htpasswd file instead of ldap + # see 'man htpasswd' to know how to create such file + #htpasswd => 't/lstu.passwd', + + # if you've set ldap above, the session will last `session_duration` seconds before + # the user needs to reauthenticate + # optional, default is 3600 + #session_duration => 3600, + + # allow to add a password on files, asked before allowing to download files + # optional, default is 0 + allow_pwd_on_files => 1, + + # force all files to be in "Burn after reading mode" + # optional, default is 0 + #force_burn_after_reading => 0, + + # if set, the files' URLs will always use this domain + # optional, no default + #fixed_domain => 'example.org', + + # abuse reasons + # set an integer in the abuse field of a file in the database and it will not be downloadable anymore + # the reason will be displayed to the downloader, according to the reasons you will configure here. + # optional, no default + abuse => { + 0 => 'Copyright infringment', + 1 => 'Illegal content', + }, + + # Content-Security-Policy header that will be sent by Lufi + # Set to '' to disable CSP header + # https://content-security-policy.com/ provides a good documentation about CSP. + # https://report-uri.com/home/generate provides a tool to generate a CSP header. + # optional, default is "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" + # the default value is good for `default` and `milligram` themes + #csp => "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" + + # X-Frame-Options header that will be sent by Lufi + # Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/' + # Set to '' to disable X-Frame-Options header + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + # Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly + # to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) + # optional, default is 'DENY' + #x_frame_options => 'DENY', + + # X-Content-Type-Options that will be sent by Lufi + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + # Set to '' to disable X-Content-Type-Options header + # optional, default is 'nosniff' + #x_content_type_options => 'nosniff', + + # X-XSS-Protection that will be sent by Lufi + # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection + # Set to '' to disable X-XSS-Protection header + # optional, default is '1; mode=block' + #x_xss_protection => '1; mode=block', + + ######################### + # Lufi cron jobs settings + ######################### + + # number of days senders' IP addresses are kept in database + # after that delay, they will be deleted from database (used with script/lufi cron cleanbdd) + # optional, default is 365 + #keep_ip_during => 365, + + # max size of the files directory, in octets + # used by script/lufi cron watch to trigger an action + # optional, no default + #max_total_size => 10*1024*1024*1024, + + # default action when files directory is over max_total_size (used with script/lufi cron watch) + # valid values are 'warn', 'stop-upload' and 'delete' + # please, see readme + # optional, default is 'warn' + #policy_when_full => 'warn', + + # images which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task + # if delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted + # optional, no default + #delete_no_longer_viewed_files => 90, +}; diff --git a/t/test.t b/t/test.t new file mode 100644 index 0000000..b0d422e --- /dev/null +++ b/t/test.t @@ -0,0 +1,237 @@ +# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: +use Mojo::Base -strict; +use Mojo::File; +use Mojo::JSON qw(to_json from_json true); +use Mojolicious; + +use Test::More; +use Test::Mojo; + +use Lufi::DB::File; +use Lufi::DB::Slice; +use FindBin qw($Bin); + +my ($m, $cfile, $config_orig, $config_file, $config_content); + +my $msg = to_json { + "total" => 1, + "part" => 0, + "size" => 7, + "name" => "foobar.txt", + "type" => "text/plain", + "delay" => "0", + "del_at_first_view" => 0, + "id" => undef, + "i" => 0 +}; +my $encrypted = '"{\\"iv\\":\\"2RGAviAeYybBqcLCmnqlgA==\\",\\"v\\":1,\\"iter\\":10000,\\"ks\\":128,\\"ts\\":64,\\"mode\\":\\"ccm\\",\\"adata\\":\\"\\",\\"cipher\\":\\"aes\\",\\"salt\\":\\"1dvKtbZ8hxA=\\",\\"ct\\":\\"w9wDZCwNSyH/yL7q1GW5fPSdi+w=\\"}"'; +my $encrypted_rgx = $encrypted; +$encrypted_rgx =~ s@\\@\\\\@g; +$encrypted_rgx =~ s@\+@\\+@g; +$encrypted_rgx =~ s@(\{|\})@\\$1@g; + +BEGIN { + use lib 'lib'; + $m = Mojolicious->new; + $cfile = Mojo::File->new($Bin, '..', 'lutim.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 = $m->plugin( + 'Config' => { + file => $cfile->to_abs->to_string, + default => { + prefix => '/', + provisioning => 100, + provis_step => 5, + length => 10, + token_length => 32, + secrets => ['hfudsifdsih'], + default_delay => 0, + max_delay => 0, + mail => { + how => 'sendmail' + }, + mail_sender => 'no-reply@lufi.io', + theme => 'default', + upload_dir => 'files', + session_duration => 3600, + allow_pwd_on_files => 0, + dbtype => 'sqlite', + db_path => 'lufi.db', + force_burn_after_reading => 0, + x_frame_options => 'DENY', + x_content_type_options => 'nosniff', + x_xss_protection => '1; mode=block', + } + } + ); + $m->plugin('Lufi::Plugin::Helpers'); + $m->plugin('DebugDumperHelper'); +} ## end BEGIN + +Lufi::DB::Slice->new(app => $m)->delete_all; +Lufi::DB::File->new(app => $m)->delete_all; + +my $t = Test::Mojo->new('Lufi'); + +## Wait for short generation +sleep 3; + +## Let's go +$t->get_ok('/') + ->status_is(200) + ->content_like(qr@Lufi@i); + +test_upload_file(); +test_download_file(); + +## Test htpasswd +switch_to_htpasswd(); +auth_test_suite('luc', 'toto'); +restore_config(); + +## Test LDAP +switch_to_ldap(); +auth_test_suite('zoidberg', 'zoidberg'); +restore_config(); + +done_testing(); + +###### +### Functions +## +sub test_upload_file { + $t->websocket_ok('/upload/') + ->send_ok($msg.'XXMOJOXX'.$encrypted) + ->message_ok + ->message_like(qr@"created_at":\d+@) + ->message_like(qr@"del_at_first_view":false@) + ->message_like(qr@"delay":"0"@) + ->message_like(qr@"duration":0@) + ->message_like(qr@"i":0@) + ->message_like(qr@"j":0@) + ->message_like(qr@"name":"foobar\.txt"@) + ->message_like(qr@"parts":1@) + ->message_like(qr@"sent_delay":"0"@) + ->message_like(qr@"short":"[^"]+"@) + ->message_like(qr@"size":7@) + ->message_like(qr@"success":true@) + ->message_like(qr@"token":"[^"]+"}@) + ->finish_ok; +} + +sub test_download_file { + my $ws_msg; + $t->ua->websocket_p('/upload/')->then(sub { + my $tx = shift; + my $promise = Mojo::Promise->new; + $tx->on(finish => sub { $promise->resolve }); + $tx->on(message => sub { + my $tx = shift; + $ws_msg = shift; + $tx->finish; + }); + $tx->send($msg.'XXMOJOXX'.$encrypted); + return $promise; + })->catch(sub { + my $err = shift; + is($err, undef); + })->wait; + + $ws_msg = from_json($ws_msg); + $t->websocket_ok('/download/'.$ws_msg->{short}) + ->send_ok(to_json({part => 0})) + ->message_ok + ->message_like(qr@"total":1@) + ->message_like(qr@"part":0@) + ->message_like(qr@"i":0@) + ->message_like(qr@"id":null@) + ->message_like(qr@"del_at_first_view":0@) + ->message_like(qr@"delay":"0"@) + ->message_like(qr@"name":"foobar\.txt"@) + ->message_like(qr@"size":7@) + ->message_like(qr@"type":"text\\/plain"@) + ->message_like(qr@XXMOJOXX@) + ->message_like(qr@$encrypted_rgx@) + ->send_ok(to_json({ended => true})) + ->finish_ok; +} + +sub auth_test_suite { + my ($login, $pass) = @_; + + $t->get_ok('/') + ->status_is(302) + ->header_is(Location => '/login'); + + test_fail_upload(); + test_login($login, $pass); + test_upload_file(); + test_download_file(); + + $t->get_ok('/logout') + ->status_is(200) + ->content_like(qr@You have been successfully logged out\.@); + + test_fail_upload(); +} + +sub test_fail_upload { + # An empty message would make it fail if we were allowed to go in the authenticated part + $t->websocket_ok('/upload/') + ->send_ok('') + ->finish_ok; +} + +sub test_login { + my ($login, $pass) = @_; + $t->get_ok('/login') + ->status_is(200) + ->content_like(qr@Signin@); + + $t->post_ok('/login' => form => { login => $login, password => $pass }) + ->status_is(302) + ->header_is(Location => '/'); + + $t->get_ok('/login') + ->status_is(302) + ->header_is(Location => '/'); +} + +sub restore_config { + $config_file->spurt($config_orig); +} + +sub switch_to_htpasswd { + $config_file = Mojo::File->new($cfile->to_abs->to_string); + $config_content = $config_file->slurp; + $config_orig = $config_content; + $config_content =~ s/#?htpasswd.*/htpasswd => 't\/lufi.passwd',/gm; + $config_file->spurt($config_content); + + Lufi::DB::Slice->new(app => $m)->delete_all; + Lufi::DB::File->new(app => $m)->delete_all; + + $t = Test::Mojo->new('Lufi'); + + ## Wait for short generation + sleep 3; +} + +sub switch_to_ldap { + $config_content = $config_orig; + $config_content =~ s/^( +)#?ldap => \{ uri/$1ldap => { uri/gm; + $config_file->spurt($config_content); + + Lufi::DB::Slice->new(app => $m)->delete_all; + Lufi::DB::File->new(app => $m)->delete_all; + + $t = Test::Mojo->new('Lufi'); + + ## Wait for short generation + sleep 3; +} diff --git a/themes/default/lib/Lufi/I18N/ca.po b/themes/default/lib/Lufi/I18N/ca.po index 8b608ea..3cfb7cd 100644 --- a/themes/default/lib/Lufi/I18N/ca.po +++ b/themes/default/lib/Lufi/I18N/ca.po @@ -93,15 +93,15 @@ msgstr "Copia tots els enllaços al porta-retalls" msgid "Copy to clipboard" msgstr "Copia al porta-retalls" -#: lib/Lufi/Controller/Files.pm:453 +#: lib/Lufi/Controller/Files.pm:460 msgid "Could not delete the file. You are not authenticated." msgstr "No es pot esborrar el fitxer. No esteu autenticat." -#: lib/Lufi/Controller/Files.pm:435 +#: lib/Lufi/Controller/Files.pm:442 msgid "Could not find the file. Are you sure of the URL and the token?" msgstr "No es troba el fitxer. Esteu segur de la URL i el testimoni?" -#: lib/Lufi/Controller/Files.pm:346 +#: lib/Lufi/Controller/Files.pm:353 msgid "Could not find the file. Are you sure of the URL?" msgstr "No trobo el fitxer. Esteu segurs de la URL?" @@ -161,15 +161,15 @@ msgstr "correus electrònics" msgid "Encrypting part XX1 of XX2" msgstr "S'està xifrant la part XX1 de XX2" -#: lib/Lufi/Controller/Files.pm:235 +#: lib/Lufi/Controller/Files.pm:242 msgid "Error: the file existed but was deleted." msgstr "Error: el fitxer existia però va ser eliminat." -#: lib/Lufi/Controller/Files.pm:315 +#: lib/Lufi/Controller/Files.pm:322 msgid "Error: the file has not been sent entirely." msgstr "Error: el fitxer no s'ha enviat del tot." -#: lib/Lufi/Controller/Files.pm:325 +#: lib/Lufi/Controller/Files.pm:332 msgid "Error: unable to find the file. Are you sure of your URL?" msgstr "Error: no trobo el fitxer. Esteu segur de la URL ?" @@ -185,7 +185,7 @@ msgstr "Expira el" msgid "Export localStorage data" msgstr "Exporta dades a l'emmagatzematge local" -#: lib/Lufi/Controller/Files.pm:417 +#: lib/Lufi/Controller/Files.pm:424 msgid "File deleted" msgstr "Fitxer eliminat" @@ -383,7 +383,7 @@ msgstr "El cos del correu no pot estar buit." msgid "The email subject can't be empty." msgstr "L'assumpte dle correu no pot estar buit." -#: lib/Lufi/Controller/Files.pm:414 +#: lib/Lufi/Controller/Files.pm:421 msgid "The file has already been deleted" msgstr "El fitxer ja ha estat esborrat" @@ -412,7 +412,7 @@ msgstr "L'autor original (i per ara l'únic) és