From b68f20597c1047f08f68af729baf2320fddd0f44 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy Date: Sat, 30 May 2015 16:33:55 +0200 Subject: [PATCH] Bug 14296: Add checks for upload quota Content-Type: text/plain; charset=utf-8 This patch includes: [1] A new routine CheckQuota in module UploadedFiles. [2] Unit tests for CheckQuota in Uploadedfiles.t [3] A check of the quota before uploading a file in upload.pl and a warning in the template if you exceeded these quota already. Test plan: [1] Delete all records from upload_settings (if any). [2] Upload a file. No restrictions. [3] Add a record in upload_settings with type=spacequota, count1=5000 and count2=1. (Yes, an interface will be added.) [4] Upload a file smaller than 5000 bytes. No restriction. [5] Upload another file making sure that the size of both files exceeds 5000 bytes. Your upload should be stopped. [6] Run the unit test with additional item and space quota tests. --- C4/UploadedFiles.pm | 76 ++++++++++++++++++-- cataloguing/value_builder/upload.pl | 5 +- .../en/modules/cataloguing/value_builder/upload.tt | 5 +- t/db_dependent/UploadedFiles.t | 54 +++++++++++++- 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/C4/UploadedFiles.pm b/C4/UploadedFiles.pm index 6fdeae0..67b2992 100644 --- a/C4/UploadedFiles.pm +++ b/C4/UploadedFiles.pm @@ -355,15 +355,79 @@ sub httpheaders { sub MaxUploadSize { my ( $user ) = @_; + my $s = _get_settings( $user, 'filesize', 1); + return @$s? $s->[0]->{count1}: MAX_DEFAULT_UPLOAD; +} + +=head2 CheckQuota + + Checks upload quota for a staff user. + If we do not find quota, we allow uploading. + We use this simple approach: if user exceeds a rule now, disallow. + Returns 1 if we allow a new upload, undef if we do not. + +=cut + +sub CheckQuota { + my ( $user ) = @_; + + # First: check number of uploaded items + my $iq = _get_settings( $user, 'itemquota' ); + foreach my $qr ( @$iq ) { + my $spent = _calc_uploaded( $user, $qr->{count2}, 'items' ); + return if $spent >= $qr->{count1}; + } + + # Second: check size of uploaded items + my $sq = _get_settings( $user, 'spacequota' ); + foreach my $qr ( @$sq ) { + my $spent = _calc_uploaded( $user, $qr->{count2}, 'bytes' ); + return if $spent >= $qr->{count1}; + } + + return 1; +} + +# internal routines + +sub _get_settings { + my ( $user, $type, $limit ) = @_; my $dbh = C4::Context->dbh; + my $count2= $type eq 'filesize'? '': ', count2, borrowernumber'; + $limit = $limit? "LIMIT $limit": ''; + my $query = qq| +SELECT count1$count2 FROM upload_settings +WHERE (borrowernumber=? OR borrowernumber IS NULL) AND type=? +ORDER BY IFNULL(borrowernumber, 0) DESC $limit + |; + my $attr = { Slice => {} }; + my $res = $dbh->selectall_arrayref( $query, $attr, ( $user, $type ) ); + + # For itemquota and spacequota we delete general settings if we found + # personal settings too. General settings have no borrowernumber. + # Since we ordered the record, the first record tells us if we have + # personal settings. + if( $type =~ /(item|space)quota/ && @$res && $res->[0]->{borrowernumber} ) { + $res = [ grep { $_->{borrowernumber} } @$res ]; + } + return $res; +} + +sub _calc_uploaded { + my ( $user, $days, $type ) = @_; + # type==items: count number; type==bytes: count size + + my $dbh = C4::Context->dbh; + my $aggr = $type eq 'items'? 'COUNT(id)': + ( $type eq 'bytes'? 'SUM(IFNULL(filesize,0))': ''); + return 0 if !$aggr; #unknown type + my $query = qq| -SELECT count1 FROM upload_settings -WHERE (borrowernumber=? OR borrowernumber IS NULL) AND - type='filesize' -ORDER BY IFNULL(borrowernumber, 0) DESC; +SELECT $aggr FROM uploaded_files +WHERE owner=? AND dtcreated + INTERVAL ? DAY > NOW() |; - my ( $max ) = $dbh->selectrow_array( $query, undef, ( $user ) ); - return $max // MAX_DEFAULT_UPLOAD; + my @temp = $dbh->selectrow_array( $query, undef, ( $user, $days ) ); + return @temp? ($temp[0] // 0): 0; } 1; diff --git a/cataloguing/value_builder/upload.pl b/cataloguing/value_builder/upload.pl index 01428fb..7b83596 100755 --- a/cataloguing/value_builder/upload.pl +++ b/cataloguing/value_builder/upload.pl @@ -84,7 +84,10 @@ my $launcher = sub { # Dealing with the uploaded file my $dir = $input->param('dir'); - if ($uploaded_file and $dir) { + my $q_ok = $template_name =~ /delete/ || C4::UploadedFiles::CheckQuota( $loggedinuser ); + if( !$q_ok ) { + $template->param( quota_exceeded => 1 ); + } elsif( $uploaded_file && $dir) { my $fh = $input->upload('uploaded_file'); $id = C4::UploadedFiles::UploadFile($uploaded_file, $dir, $fh->handle); diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/upload.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/upload.tt index 2a74817..83eab22 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/upload.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/value_builder/upload.tt @@ -57,7 +57,10 @@ return true; [% ELSE %] - [% IF ( MissingURL ) %] + [% IF ( quota_exceeded ) %] +

Sorry, you exceed the upload quota at this moment. Please contact your system administrator for more information or adjusting your quota.

+

+ [% ELSIF ( MissingURL ) %]

Error: The OPAC system preference OPACBaseURL is not configured.

[% ELSIF ( error ) %] diff --git a/t/db_dependent/UploadedFiles.t b/t/db_dependent/UploadedFiles.t index 1d85970..eb9b4ee 100644 --- a/t/db_dependent/UploadedFiles.t +++ b/t/db_dependent/UploadedFiles.t @@ -3,7 +3,7 @@ use Modern::Perl; use File::Temp qw/tempdir/; use Test::CGI::Multipart; -use Test::More tests => 21; +use Test::More tests => 22; use Test::Warn; use t::lib::Mocks; @@ -79,6 +79,9 @@ is( @{$a->[0]->{dirs}} == 2, 1, 'Finddir returns subdirectories' ); subtest 'Tests for MaxUploadSize' => sub { test_max(); }; +subtest 'Tests for CheckQuota' => sub { + test_quota(); +}; #end of game $dbh->rollback; @@ -110,3 +113,52 @@ sub test_max { is( $max, 500, 'MaxUploadSize is still equal to default for user 2' ); done_testing(4); } + +sub test_quota { + $dbh->do("DELETE FROM upload_settings"); + $dbh->do("DELETE FROM uploaded_files"); + is( C4::UploadedFiles::CheckQuota(1), 1, 'Trivial quota check' ); + + my @bor = $dbh->selectrow_array( + 'SELECT borrowernumber FROM borrowers LIMIT 1' ); + if( !@bor ) { + done_testing(1); + return; + } + + # We now load some item rules: general 2 in 3 days and two rules + # for specific borrower (2 per day, 3 in 2 days) + $dbh->do("INSERT INTO upload_settings + ( borrowernumber, type, count1, count2 ) + VALUES (?, ?, ?, ?)", undef, ( undef, 'itemquota', 2, 3 )); + $dbh->do("INSERT INTO upload_settings + ( borrowernumber, type, count1, count2 ) + VALUES (?, ?, ?, ?)", undef, ( $bor[0], 'itemquota', 2, 1 )); + $dbh->do("INSERT INTO upload_settings + ( borrowernumber, type, count1, count2 ) + VALUES (?, ?, ?, ?)", undef, ( $bor[0], 'itemquota', 3, 2 )); + + # check item quota + is( C4::UploadedFiles::CheckQuota($bor[0]), 1, 'Nothing uploaded so far' ); + my $ins1= qq|INSERT INTO uploaded_files ( id, owner, filesize, dtcreated ) VALUES (?, ?, ?, NOW() + INTERVAL ? HOUR)|; + $dbh->do( $ins1, undef, ( 'aa1', $bor[0], 1000, -25) ); + is( C4::UploadedFiles::CheckQuota($bor[0]), 1, 'One uploaded' ); + $dbh->do( $ins1, undef, ( 'aa2', $bor[0], 1000, -25) ); + is( C4::UploadedFiles::CheckQuota($bor[0]), 1, 'Two uploaded' ); + $dbh->do( $ins1, undef, ( 'aa3', $bor[0], 1000, 0) ); + is( C4::UploadedFiles::CheckQuota($bor[0]), undef, 'Max items reached' ); + + # Now delete the last upload and add a size rule (5000 per 2 days) + # Gradually update the size of the second uploaded file + $dbh->do("DELETE FROM uploaded_files WHERE id=?", undef, ('aa3') ); + $dbh->do("INSERT INTO upload_settings + ( borrowernumber, type, count1, count2 ) + VALUES (?, ?, ?, ?)", undef, ( $bor[0], 'spacequota', 5000, 2 )); + is( C4::UploadedFiles::CheckQuota($bor[0]), 1, 'Size 2000' ); + $dbh->do("UPDATE uploaded_files SET filesize=? WHERE id=?", undef, (3000, 'aa2') ); + is( C4::UploadedFiles::CheckQuota($bor[0]), 1, 'Size 4000' ); + $dbh->do("UPDATE uploaded_files SET filesize=? WHERE id=?", undef, (4000, 'aa2') ); + is( C4::UploadedFiles::CheckQuota($bor[0]), undef, 'Size 5000 reached' ); + + done_testing(8); +} -- 1.7.10.4