# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: package Lutim; use Mojo::Base 'Mojolicious'; use Mojo::IOLoop; use Lutim::DB::Image; use Lutim::DefaultConfig qw($default_config); use vars qw($im_loaded); BEGIN { eval "use Image::Magick"; if ($@) { warn "You don't have Image::Magick installed so you won't have thumbnails."; $im_loaded = 0; } else { $im_loaded = 1; } } $ENV{MOJO_TMPDIR} = 'tmp' unless (defined $ENV{MOJO_TMPDIR}); mkdir($ENV{MOJO_TMPDIR}, 0700) unless (-d $ENV{MOJO_TMPDIR}); # This method will run once at server start sub startup { my $self = shift; $self->{wait_for_it} = {}; push @{$self->commands->namespaces}, 'Lutim::Command'; $self->plugin('DebugDumperHelper'); my $config = $self->plugin('Config', { default => $default_config }); if ($config->{watermark_path}) { die sprintf('%s does not exist or is not readable.', $config->{watermark_path}) unless -r $config->{watermark_path}; my $valid = { center => 1, north => 1, northeast => 1, east => 1, southeast => 1, south => 1, southwest => 1, west => 1, northwest => 1 }; die sprintf('%s is not a valid value for watermark_placement.', $config->{watermark_placement}) unless $valid->{lc($config->{watermark_placement})}; $valid = { 'tiling' => 1, 'single' => 1, 'none' => 1 }; die sprintf('%s is not a valid value for watermark_default.', $config->{watermark_default}) unless $valid->{lc($config->{watermark_default})}; die sprintf('%s is not a valid value for watermark_enforce.', $config->{watermark_enforce}) unless $valid->{lc($config->{watermark_enforce})}; } 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})); $ENV{MOJO_MAX_MESSAGE_SIZE} = $config->{max_file_size}; $self->secrets($config->{secrets}); # Themes handling shift @{$self->renderer->paths}; shift @{$self->static->paths}; if ($config->{theme} ne 'default') { my $theme = $self->home->rel_file('themes/'.$config->{theme}); push @{$self->renderer->paths}, $theme.'/templates' if -d $theme.'/templates'; push @{$self->static->paths}, $theme.'/public' if -d $theme.'/public'; } push @{$self->renderer->paths}, $self->home->rel_file('themes/default/templates'); push @{$self->static->paths}, $self->home->rel_file('themes/default/public'); # Internationalization my $lib = $self->home->rel_file('themes/'.$config->{theme}.'/lib'); eval qq(use lib "$lib"); $self->plugin('I18N'); # Static assets gzipping $self->plugin('GzipStatic'); # Headers $self->plugin('Lutim::Plugin::Headers'); # Helpers $self->plugin('Lutim::Plugin::Helpers'); $self->plugin('Lutim::Plugin::Lang'); # Create directory if needed mkdir($self->config('upload_dir'), 0700) unless (-d $self->config('upload_dir')); die ('The upload directory ('.$self->config('upload_dir').') is not writable') unless (-w $self->config('upload_dir')); # Minion if ($config->{minion}->{enabled}) { $self->config->{minion}->{dbtype} = 'sqlite' unless defined $config->{minion}->{dbtype}; if ($config->{minion}->{dbtype} eq 'sqlite') { $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( accessed => sub { my $job = shift; my $short = $job->args->[0]; my $time = $job->args->[1]; my $img = Lutim::DB::Image->new(app => $job->app, short => $short); $img->accessed($time) if $img->path; } ); } # Hooks $self->hook( before_dispatch => sub { my $c = shift; $c->stop_upload(); # API allowed domains if (defined($c->config->{allowed_domains})) { if ($c->config->{allowed_domains}->[0] eq '*') { $c->res->headers->header('Access-Control-Allow-Origin' => '*'); } elsif (my $origin = $c->req->headers->origin) { for my $domain ($c->config->{allowed_domains}) { if ($domain->[0] eq $origin) { $c->res->headers->header('Access-Control-Allow-Origin' => $origin); last; } } } } # Scheme detection if ((defined($c->req->headers->header('X-Forwarded-Proto')) && $c->req->headers->header('X-Forwarded-Proto') eq 'https') || $c->config->{https}) { $c->req->url->base->scheme('https'); } } ); # Recurrent tasks Mojo::IOLoop->recurring(5 => sub { my $loop = shift; $self->provisioning(); # Purge expired anti-flood protection my $wait_for_it = $self->{wait_for_it}; 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($config->{ldap}) || defined($config->{htpasswd})) { if (defined($config->{ldap})) { require Net::LDAP; } if (defined($config->{htpasswd})) { require Apache::Htpasswd; } die sprintf('Unable to read %s', $config->{htpasswd}) if (defined($config->{htpasswd}) && !-r $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; } } ); $self->app->sessions->default_expiration($config->{session_duration}); } $self->defaults(layout => 'default'); $self->provisioning(); # Router my $r = $self->routes; $r->add_condition(authorized => sub { my ($r, $c, $captures) = @_; return 1 unless (defined($config->{ldap}) || defined($config->{htpasswd})); return $c->is_user_authenticated; }); $r->options(sub { my $c = shift; $c->res->headers->allow('POST') if (defined($c->config->{allowed_domains})); $c->render(data => '', status => 204); }); $r->get('/')-> requires('authorized')-> to('Image#home')-> name('index'); $r->get('/')-> to('Authent#index'); if (defined $config->{ldap} || defined $config->{htpasswd}) { # Login page $r->get('/login') ->to('Authent#index') ->name('login'); # Authentication $r->post('/login') ->to('Authent#login'); # Logout page $r->get('/logout') ->to('Authent#log_out') ->name('logout'); } $r->get('/about')-> to('Image#about')-> name('about'); $r->get('/infos')-> to('Image#infos')-> name('infos'); $r->get('/stats')-> to('Image#stats')-> name('stats'); $r->get('/lang/:l')-> to('Image#change_lang')-> name('lang'); $r->get('/partial/<:file>.<:f>' => sub { my $c = shift; $c->render( template => 'partial/'.$c->param('file'), format => 'js', layout => undef, d => { delay_0 => $c->l('no time limit'), delay_1 => $c->l('24 hours'), delay_365 => $c->l('1 year') } ); })->name('partial'); $r->get('/gallery' => sub { shift->render( template => 'gallery', ); })->name('gallery'); $r->get('/myfiles')-> requires('authorized')-> name('myfiles'); $r->get('/myfiles')-> to('Authent#index'); $r->get('/manifest.webapp')-> to('Image#webapp')-> name('manifest.webapp'); $r->get('/zip') ->to('Image#zip') ->name('zip'); $r->get('/random') ->to('Image#random') ->name('random'); $r->post('/')-> requires('authorized')-> to('Image#add')-> name('add'); $r->post('/')-> to('Authent#index'); $r->get('/d/:short/:token')-> requires('authorized')-> to('Image#delete')-> name('delete'); $r->get('/d/:short/:token')-> to('Authent#index'); $r->post('/m/:short/:token')-> requires('authorized')-> to('Image#modify')-> name('modify'); $r->post('/m/:short/:token')-> to('Authent#index'); $r->post('/c')-> requires('authorized')-> to('Image#get_counter')-> name('counter'); $r->post('/c')-> to('Authent#index'); $r->get('/about/<:short>')-> to('Image#about_img')-> name('about_img'); $r->get('/about/<:short>.<:f>')-> to('Image#about_img')-> name('about_img'); $r->get('/about/:short/<:key>.<:f>')-> to('Image#about_img')-> name('about_img'); $r->get('/<:short>.<:f>')-> to('Image#short')-> name('short'); $r->get('/:short')-> to('Image#short'); $r->get('/:short/<:key>.<:f>')-> to('Image#short'); $r->get('/:short/:key')-> to('Image#short'); } 1;