Commit 3feeeac3 authored by Luc Didry's avatar Luc Didry
Browse files

Merge branch 'development' into 'master'

Time for a new release

Closes #82, #61, #84, #67, #70, #5, #76, #79, #56, #77, #78, #48, #80, #83 et #81

See merge request luc/lutim!51
parents 4c53669c f7b81e70
Pipeline #64867 passed with stages
in 4 minutes and 27 seconds
......@@ -13,5 +13,8 @@ themes/*
themes/default/templates/data.html.ep
themes/default/templates/raw.html.ep
themes/default/templates/stats.json.ep
themes/default/templates/partial/raw.js.ep
tmp/*
.zanata-cache/*
cover_db/*
*.passwd
image: hatsoftwares/test-ci:latest
image: hatsoftwares/lutim-test-ci:latest
stages:
- sqlite
- postgresql
- podcheck
- carton
- carton_bdd
- tests
- cover
before_script:
- carton install
- rm -f *db
sqlite1:
stage: sqlite
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
- rm -f *.db
variables:
POSTGRES_DB: lutim_db
POSTGRES_USER: lutim
POSTGRES_PASSWORD: lutim_pwd
### Jobs templates
##
#
.carton_bdd_template: &carton_bdd_definition
stage: carton_bdd
retry: 2
artifacts:
paths:
- local/
dependencies:
- carton
.sqlite_template: &sqlite_definition
stage: tests
retry: 2
artifacts:
paths:
- cover_db/
dependencies:
- carton_sqlite
.pg_template: &pg_definition
stage: tests
retry: 2
artifacts:
paths:
- local
- cover_db/
dependencies:
- carton_postgresql
services:
- name: postgres:9.6
alias: postgres
### Podcheck
##
#
podcheck:
stage: podcheck
script:
- make podcheck
### Install common dependencies
##
#
carton:
stage: carton
artifacts:
paths:
- local/
dependencies: []
script:
- carton install --deployment --without=sqlite --without=postgresql --without=minion --without=cache --without=memcached
when: always
retry: 2
### Install DB related dependencies
##
#
carton_sqlite:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=postgresql --without=minion --without=cache --without=memcached
carton_postgresql:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=sqlite --without=minion --without=cache --without=memcached
### SQLite tests
##
#
sqlite1:
<<: *sqlite_definition
script:
- carton install --deployment --without=postgresql --without=minion --without=cache --without=memcached
- MOJO_CONFIG=t/sqlite1.conf make test-sqlite
- MOJO_CONFIG=t/sqlite1.conf make watch
- MOJO_CONFIG=t/sqlite1.conf make cleanbdd
- MOJO_CONFIG=t/sqlite1.conf make cleanfiles
- MOJO_CONFIG=t/sqlite1.conf make stats
sqlite2:
stage: sqlite
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
<<: *sqlite_definition
script:
- make podcheck
- carton install --deployment --without=postgresql --without=cache --without=memcached
- MOJO_CONFIG=t/sqlite2.conf make minion &
- sleep 3
- MOJO_CONFIG=t/sqlite2.conf make test-sqlite
......@@ -36,18 +101,13 @@ sqlite2:
- MOJO_CONFIG=t/sqlite2.conf make cleanfiles
- MOJO_CONFIG=t/sqlite2.conf make stats
sqlite3:
stage: sqlite
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
<<: *sqlite_definition
services:
- name: postgres:9.6
alias: postgres
script:
- make podcheck
- service postgresql restart
- sleep 10
- service postgresql status
- make create-pg-test-db
- carton install --deployment --without=cache --without=memcached
- export PGPASSWORD=lutim_pwd; echo 'CREATE DATABASE lutim_minion WITH OWNER lutim;' | psql -h postgres -U lutim lutim_db
- MOJO_CONFIG=t/sqlite3.conf make minion &
- sleep 3
- MOJO_CONFIG=t/sqlite3.conf make test-sqlite
......@@ -55,40 +115,23 @@ sqlite3:
- MOJO_CONFIG=t/sqlite3.conf make cleanbdd
- MOJO_CONFIG=t/sqlite3.conf make cleanfiles
- MOJO_CONFIG=t/sqlite3.conf make stats
tags:
- Debian
- Jessie
### PostgreSQL tests
##
#
postgresql1:
stage: postgresql
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
<<: *pg_definition
script:
- make podcheck
- service postgresql restart
- sleep 10
- service postgresql status
- make create-pg-test-db
- carton install --deployment --without=sqlite --without=minion --without=cache --without=memcached
- MOJO_CONFIG=t/postgresql1.conf make test-pg
- MOJO_CONFIG=t/postgresql1.conf make watch
- MOJO_CONFIG=t/postgresql1.conf make cleanbdd
- MOJO_CONFIG=t/postgresql1.conf make cleanfiles
- MOJO_CONFIG=t/postgresql1.conf make stats
postgresql2:
stage: postgresql
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
<<: *pg_definition
script:
- make podcheck
- service postgresql restart
- sleep 10
- service postgresql status
- make create-pg-test-db
- carton install --deployment --without=cache --without=memcached
- MOJO_CONFIG=t/postgresql2.conf make minion &
- sleep 3
- MOJO_CONFIG=t/postgresql2.conf make test-pg
......@@ -97,18 +140,10 @@ postgresql2:
- MOJO_CONFIG=t/postgresql2.conf make cleanfiles
- MOJO_CONFIG=t/postgresql2.conf make stats
postgresql3:
stage: postgresql
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
<<: *pg_definition
script:
- make podcheck
- service postgresql restart
- sleep 10
- service postgresql status
- make create-pg-test-db
- carton install --deployment --without=sqlite --without=cache --without=memcached
- export PGPASSWORD=lutim_pwd; echo 'CREATE DATABASE lutim_minion WITH OWNER lutim;' | psql -h postgres -U lutim lutim_db
- MOJO_CONFIG=t/postgresql3.conf make minion &
- sleep 3
- MOJO_CONFIG=t/postgresql3.conf make test-pg
......@@ -116,6 +151,27 @@ postgresql3:
- MOJO_CONFIG=t/postgresql3.conf make cleanbdd
- MOJO_CONFIG=t/postgresql3.conf make cleanfiles
- MOJO_CONFIG=t/postgresql3.conf make stats
tags:
- Debian
- Jessie
### Code coverage
##
#
cover:
stage: cover
script:
- make cover
coverage: '/Total .*\d+\.\d+$/'
### Push new translations strings to https://trad.framasoft.org
##
#
trads:
stage: cover
image: framasoft/push-trad:latest
dependencies: []
script:
- sed -e "s@<project-version>.*</project-version>@<project-version>$CI_COMMIT_REF_SLUG</project-version>@" -i zanata.xml
- if [ ! -z ${ZANATA_CONFIG+x} ]; then mkdir -p ${HOME}/.config; echo -e "${ZANATA_CONFIG}" > ${HOME}/.config/zanata.ini; fi
- if [ ! -z ${ZANATA_CONFIG+x} ]; then make push-locales; fi
only:
- development
- master
Revision history for Lutim
0.11.0 2018-??-??
- Allow to install only needed deps
- Notification when copying to clipboard or deleting images
- Allow to use memcached as cache system
- Use Mojolicious::Plugin::Chi
- Gzip static assets with Mojolicious::Plugin::GzipStatic
- Fix scroll-to-top when clicking on delete image
- Add CSP header
- Add X-Content-Type-Options, X-XSS-Protection, X-Frame-Options headers
- Remove @framasky as default tweet_card_via setting
- Add a message saying how many images there is in the gallery
- Use ISO::639_1 for languages' native names
- Add CLI command to print informations about images
- Add CLI command to remove images
- Add CLI command to search images based on the uploader's IP address
- Add .zip file check in the tests
- Fix stats files place when using non-default theme
- Allow to use a fixed domain
- Add localStorage export and import feature
- Add link to generate random file in collection
- Add modal to modify expiration delay from "myfiles" page
- Add optional authentication (LDAP or htpasswd file)
0.10.4 2018-05-07
- Fix bug in cache system that would allow someone to view an image with an incorrect decryption key
......
......@@ -4,15 +4,29 @@ XGETTEXT=carton exec local/bin/xgettext.pl -u
CARTON=carton exec
LUTIM=script/lutim
REAL_LUTIM=script/application
HEAD := $(shell git rev-parse --abbrev-ref HEAD)
minify:
@echo "CSS concatenation"
@cd ./themes/default/public/css/ && cat bootstrap.min.css fontello.css hennypenny.css lutim.css toastify.css | csso > common.min.css
@cd ./themes/default/public/css/ && cat animation.css uploader.css markdown.css | csso > not_stats.min.css
@cd ./themes/default/public/css/ && cat photoswipe.css default-skin/default-skin.css | csso > gallery.min.css
@cd ./themes/default/public/css/ && cat twitter.css | csso > twitter.min.css
locales:
$(XGETTEXT) $(EXTRACTDIR) -o $(POT) 2>/dev/null
push-locales: locales
zanata-cli -q -B push
push-locales:
ifeq ($(HEAD),$(filter $(HEAD),master development))
sed -e 's@<project-version>.*</project-version>@<project-version>$(HEAD)</project-version>@' -i zanata.xml && \
zanata-cli -q -B push
endif
pull-locales:
zanata-cli -q -B pull
ifeq ($(HEAD),$(filter $(HEAD),master development))
sed -e 's@<project-version>.*</project-version>@<project-version>$(HEAD)</project-version>@' -i zanata.xml && \
zanata-cli -q -B pull
endif
stats-locales:
zanata-cli -q stats
......@@ -20,18 +34,21 @@ stats-locales:
podcheck:
podchecker lib/Lutim/DB/Image.pm
cover:
PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) cover --ignore_re '^local'
test-sqlite:
$(CARTON) $(REAL_LUTIM) test
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) $(REAL_LUTIM) test
test-pg:
$(CARTON) $(REAL_LUTIM) test
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) $(REAL_LUTIM) test
test: podcheck test-sqlite test-pg
clean:
rm -rf lutim.db files/
dev:
dev: minify
$(CARTON) morbo $(LUTIM) --listen http://0.0.0.0:3000 --watch lib/ --watch script/ --watch themes/ --watch lutim.conf
devlog:
......
......@@ -3,20 +3,16 @@ requires 'EV';
requires 'IO::Socket::SSL';
requires 'Net::SSLeay', '>= 1.81';
requires 'Data::Validate::URI';
requires 'Net::Domain::TLD', '>= 1.73'; # Must have the last version to handle (at least) .xyz and .link
requires 'Mojo::Pg';
requires 'Mojo::SQLite';
requires 'Net::Domain::TLD', '>= 1.75'; # Must have the last version to handle (at least) .xyz and .link
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::DebugDumperHelper';
requires 'Mojolicious::Plugin::PgURLHelper';
requires 'Mojolicious::Plugin::StaticCache';
requires "Minion", "== 4.06";
requires 'Minion::Backend::SQLite', "== 0.009";
requires 'Mojolicious::Plugin::GzipStatic';
requires 'Mojolicious::Plugin::CSPHeader';
requires 'Text::Unidecode';
requires 'DateTime';
requires 'Filesys::DiskUsage';
requires 'Switch';
requires 'Data::Validate::URI';
requires 'Crypt::CBC';
requires 'Crypt::Blowfish';
requires 'Locale::Maketext';
......@@ -27,5 +23,36 @@ requires 'Image::ExifTool';
requires 'Data::Entropy';
requires 'List::MoreUtils', '> 0.33';
requires 'Archive::Zip';
requires 'CHI';
requires 'Data::Serializer';
requires 'ISO::639_1';
feature 'postgresql', 'PostgreSQL support' => sub {
requires 'Mojo::Pg';
requires 'Mojolicious::Plugin::PgURLHelper';
};
feature 'sqlite', 'SQLite support' => sub {
requires 'Mojo::SQLite', '>= 3.000';
requires 'Minion::Backend::SQLite', '>= 4.001';
};
feature 'minion', 'Minion support' => sub {
requires 'Minion';
};
feature 'cache', 'Cache system' => sub {
requires 'Mojolicious::Plugin::CHI';
requires 'Data::Serializer';
};
feature 'memcached', 'Cache system using Memcached' => sub {
requires 'Mojolicious::Plugin::CHI';
requires 'CHI::Driver::Memcached';
requires 'Cache::Memcached';
};
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 'test' => sub {
requires 'Devel::Cover';
};
This diff is collapsed.
......@@ -3,7 +3,6 @@ package Lutim;
use Mojo::Base 'Mojolicious';
use Mojo::IOLoop;
use Lutim::DB::Image;
use CHI;
use vars qw($im_loaded);
BEGIN {
......@@ -24,49 +23,67 @@ sub startup {
$self->{wait_for_it} = {};
push @{$self->commands->namespaces}, 'Lutim::Command';
$self->plugin('DebugDumperHelper');
$self->plugin('PgURLHelper');
my $config = $self->plugin('Config', {
default => {
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
tweet_card_via => '@framasky',
max_file_size => 10*1024*1024,
https => 0,
proposed_delays => '0,1,7,30,365',
default_delay => 0,
max_delay => 0,
token_length => 24,
crypto_key_length => 8,
thumbnail_size => 100,
theme => 'default',
dbtype => 'sqlite',
db_path => 'lutim.db',
max_files_in_zip => 15,
prefix => '/',
minion => {
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
max_file_size => 10*1024*1024,
https => 0,
proposed_delays => '0,1,7,30,365',
default_delay => 0,
max_delay => 0,
token_length => 24,
crypto_key_length => 8,
thumbnail_size => 100,
theme => 'default',
dbtype => 'sqlite',
db_path => 'lutim.db',
max_files_in_zip => 15,
prefix => '/',
minion => {
enabled => 0,
dbtype => 'sqlite',
db_path => 'minion.db'
},
cache_max_size => 0,
quiet_logs => 0,
disable_img_stats => 0,
session_duration => 3600,
cache_max_size => 0,
memcached_servers => [],
quiet_logs => 0,
disable_img_stats => 0,
x_frame_options => 'DENY',
x_content_type_options => 'nosniff',
x_xss_protection => '1; mode=block',
}
});
my $cache_max_size = ($config->{cache_max_size} > 0) ? 8 * 1024 * 1024 * $config->{cache_max_size} : 1;
$self->{images_cache} = CHI->new(
driver => 'Memory',
global => 1,
is_size_aware => 1,
max_size => $cache_max_size,
expires_in => '1 day'
);
if (scalar(@{$config->{memcached_servers}})) {
$self->plugin(CHI => {
lutim_images_cache => {
driver => 'Memcached',
servers => $config->{memcached_servers},
expires_in => '1 day',
expires_on_backend => 1,
}
});
} elsif ($config->{cache_max_size} != 0) {
my $cache_max_size = 8 * 1024 * 1024 * $config->{cache_max_size};
$self->plugin(CHI => {
lutim_images_cache => {
driver => 'Memory',
global => 1,
is_size_aware => 1,
max_size => $cache_max_size,
expires_in => '1 day'
}
});
}
die "You need to provide a contact information in lutim.conf !" unless (defined($config->{contact}));
......@@ -90,8 +107,11 @@ sub startup {
eval qq(use lib "$lib");
$self->plugin('I18N');
# Cache static files
$self->plugin('StaticCache');
# Static assets gzipping
$self->plugin('GzipStatic');
# Headers
$self->plugin('Lutim::Plugin::Headers');
# Helpers
$self->plugin('Lutim::Plugin::Helpers');
......@@ -104,6 +124,7 @@ sub startup {
$self->config('minion')->{db_path} = 'minion.db' unless defined $config->{minion}->{db_path};
$self->plugin('Minion' => { SQLite => 'sqlite:'.$config->{minion}->{db_path} });
} elsif ($config->{minion}->{dbtype} eq 'postgresql') {
$self->plugin('PgURLHelper');
$self->plugin('Minion' => { Pg => $self->pg_url($config->{minion}->{'pgdb'}) });
}
$self->app->minion->add_task(
......@@ -156,6 +177,106 @@ sub startup {
delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $self->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it));
});
# Authentication (if configured)
if (defined($self->config('ldap')) || defined($self->config('htpasswd'))) {
if (defined($self->config('ldap'))) {
require Net::LDAP;
}
if (defined($self->config('htpasswd'))) {
require Apache::Htpasswd;
}
die 'Unable to read '.$self->config('htpasswd') if (defined($self->config('htpasswd')) && !-r $self->config('htpasswd'));
$self->plugin('Authentication' =>
{
autoload_user => 1,
session_key => 'Lutim',
load_user => sub {
my ($c, $username) = @_;
return $username;
},
validate_user => sub {
my ($c, $username, $password, $extradata) = @_;
if (defined($c->config('ldap'))) {
my $ldap = Net::LDAP->new($c->config->{ldap}->{uri});
my $mesg;
if (defined($c->config->{ldap}->{bind_dn}) && defined($c->config->{ldap}->{bind_pwd})) {
# connect to the ldap server using the bind credentials
$mesg = $ldap->bind(
$c->config->{ldap}->{bind_dn},
password => $c->config->{ldap}->{bind_pwd}
);
} else {
# anonymous bind
$mesg = $ldap->bind
}
if ($mesg->code) {
$c->app->log->info('[LDAP INFO] Authenticated bind failed - Login: '.$c->config->{ldap}->{bind_dn}) if defined($c->config->{ldap}->{bind_dn});
$c->app->log->error('[LDAP ERROR] Error on bind: '.$mesg->error);
return undef;
}
my $ldap_user_attr = $c->config->{ldap}->{user_attr};
my $ldap_user_filter = $c->config->{ldap}->{user_filter};
# search the ldap database for the user who is trying to login
$mesg = $ldap->search(
base => $c->config->{ldap}->{user_tree},
filter => "(&($ldap_user_attr=$username)$ldap_user_filter)"
);
if ($mesg->code) {
$c->app->log->error('[LDAP ERROR] Error on search: '.$mesg->error);
return undef;
}
# check to make sure that the ldap search returned at least one entry
my @entries = $mesg->entries;
my $entry = $entries[0];
unless (defined $entry) {
$c->app->log->info("[LDAP INFO] Authentication failed - User $username filtered out, IP: ".$c->ip);
return undef;
}
# retrieve the first user returned by the search
$c->app->log->debug("[LDAP DEBUG] Found user dn: ".$entry->dn);
# Now we know that the user exists
$mesg = $ldap->bind($entry->dn,
password => $password
);
if ($mesg->code) {
$c->app->log->info("[LDAP INFO] Authentication failed - Login: $username, IP: ".$c->ip);
$c->app->log->error('[LDAP ERROR] Authentication failed '.$mesg->error);
return undef;
}
$c->app->log->info("[LDAP INFO] Authentication successful - Login: $username, IP: ".$c->ip);
} elsif (defined($c->config('htpasswd'))) {
my $htpasswd = new Apache::Htpasswd(
{
passwdFile => $c->config('htpasswd'),
ReadOnly => 1
}
);
if (!$htpasswd->htCheckPassword($username, $password)) {
return undef;
}
$c->app->log->info("[Simple authentication successful] login: $username, IP: ".$c->ip);
}
return $username;