View | Details | Raw Unified | Return to bug 23632
Collapse All | Expand All

(-)a/C4/Log.pm (-76 / +1 lines)
Lines 35-41 use vars qw(@ISA @EXPORT); Link Here
35
BEGIN {
35
BEGIN {
36
        require Exporter;
36
        require Exporter;
37
        @ISA = qw(Exporter);
37
        @ISA = qw(Exporter);
38
        @EXPORT = qw(&logaction &cronlogaction &GetLogs);
38
        @EXPORT = qw(&logaction &cronlogaction);
39
}
39
}
40
40
41
=head1 NAME
41
=head1 NAME
Lines 120-200 sub cronlogaction { Link Here
120
    logaction( 'CRONJOBS', 'Run', undef, $loginfo ) if C4::Context->preference('CronjobLog');
120
    logaction( 'CRONJOBS', 'Run', undef, $loginfo ) if C4::Context->preference('CronjobLog');
121
}
121
}
122
122
123
=item GetLogs
124
125
$logs = GetLogs($datefrom,$dateto,$user,\@modules,$action,$object,$info,$interfaces);
126
127
Return:
128
C<$logs> is a ref to a hash which contains all columns from action_logs
129
130
=cut
131
132
sub GetLogs {
133
    my $datefrom = shift;
134
    my $dateto   = shift;
135
    my $user     = shift;
136
    my $modules  = shift;
137
    my $action   = shift;
138
    my $object   = shift;
139
    my $info     = shift;
140
    my $interfaces = shift;
141
142
    my $iso_datefrom = $datefrom ? output_pref({ dt => dt_from_string( $datefrom ), dateformat => 'iso', dateonly => 1 }) : undef;
143
    my $iso_dateto = $dateto ? output_pref({ dt => dt_from_string( $dateto ), dateformat => 'iso', dateonly => 1 }) : undef;
144
145
    $user ||= q{};
146
147
    my $dbh   = C4::Context->dbh;
148
    my $query = "
149
        SELECT *
150
        FROM   action_logs
151
        WHERE 1
152
    ";
153
154
    my @parameters;
155
    $query .=
156
      " AND DATE_FORMAT(timestamp, '%Y-%m-%d') >= \"" . $iso_datefrom . "\" "
157
      if $iso_datefrom;    #fix me - mysql specific
158
    $query .=
159
      " AND DATE_FORMAT(timestamp, '%Y-%m-%d') <= \"" . $iso_dateto . "\" "
160
      if $iso_dateto;
161
    if ( $user ne q{} ) {
162
        $query .= " AND user = ? ";
163
        push( @parameters, $user );
164
    }
165
    if ( $modules && scalar(@$modules) ) {
166
        $query .=
167
          " AND module IN (" . join( ",", map { "?" } @$modules ) . ") ";
168
        push( @parameters, @$modules );
169
    }
170
    if ( $action && scalar(@$action) ) {
171
        $query .= " AND action IN (" . join( ",", map { "?" } @$action ) . ") ";
172
        push( @parameters, @$action );
173
    }
174
    if ($object) {
175
        $query .= " AND object = ? ";
176
        push( @parameters, $object );
177
    }
178
    if ($info) {
179
        $query .= " AND info LIKE ? ";
180
        push( @parameters, "%" . $info . "%" );
181
    }
182
    if ( $interfaces && scalar(@$interfaces) ) {
183
        $query .=
184
          " AND interface IN (" . join( ",", map { "?" } @$interfaces ) . ") ";
185
        push( @parameters, @$interfaces );
186
    }
187
188
    my $sth = $dbh->prepare($query);
189
    $sth->execute(@parameters);
190
191
    my @logs;
192
    while ( my $row = $sth->fetchrow_hashref ) {
193
        push @logs, $row;
194
    }
195
    return \@logs;
196
}
197
198
1;
123
1;
199
__END__
124
__END__
200
125
(-)a/t/db_dependent/Illrequest/Logger.t (-19 lines)
Lines 27-54 use t::lib::Mocks; Link Here
27
27
28
my $schema = Koha::Database->new->schema;
28
my $schema = Koha::Database->new->schema;
29
29
30
# A mock response from C4::Log::GetLogs()
31
my $logs = [
32
    {
33
        info      => '{"log_origin": "core"}',
34
        action    => 'STATUS_CHANGE',
35
        timestamp => '2018-10-02 11:12:22'
36
    },
37
    {
38
        info      => '{"log_origin": "core"}',
39
        action    => 'STATUS_CHANGE',
40
        timestamp => '2018-10-02 11:12:12'
41
    },
42
    {
43
        info      => '{"log_origin": "core"}',
44
        action    => 'STATUS_CHANGE',
45
        timestamp => '2018-10-02 11:12:32'
46
    }
47
];
48
# Mock the modules we use
30
# Mock the modules we use
49
my $c4_log = Test::MockModule->new('C4::Log');
31
my $c4_log = Test::MockModule->new('C4::Log');
50
$c4_log->mock('logaction', sub { 1 });
32
$c4_log->mock('logaction', sub { 1 });
51
$c4_log->mock('GetLogs', sub { return $logs; });
52
my $c4_tpl = Test::MockModule->new('C4::Templates');
33
my $c4_tpl = Test::MockModule->new('C4::Templates');
53
$c4_tpl->mock('_get_template_file',
34
$c4_tpl->mock('_get_template_file',
54
    sub { return ('htdocs', 'theme', 'lang', 'base/'); });
35
    sub { return ('htdocs', 'theme', 'lang', 'base/'); });
(-)a/t/db_dependent/Koha/Patron/Messages.t (-2 / +2 lines)
Lines 22-28 use Modern::Perl; Link Here
22
use Test::More tests => 12;
22
use Test::More tests => 12;
23
23
24
use C4::Context;
24
use C4::Context;
25
use C4::Log;
25
use Koha::ActionLogs;
26
use Koha::Patron::Message;
26
use Koha::Patron::Message;
27
use Koha::Patron::Messages;
27
use Koha::Patron::Messages;
28
use Koha::Patrons;
28
use Koha::Patrons;
Lines 102-107 is( get_nb_of_logactions(), $nb_of_logaction + 3, 'With BorrowersLog on, 1 new l Link Here
102
$schema->storage->txn_rollback;
102
$schema->storage->txn_rollback;
103
103
104
sub get_nb_of_logactions {
104
sub get_nb_of_logactions {
105
    return scalar( @{ C4::Log::GetLogs( undef, undef, undef, ['MEMBERS'] ) } );
105
    return Koha::ActionLogs->search({ module => 'MEMBERS' })->count;
106
}
106
}
107
107
(-)a/t/db_dependent/Log.t (-89 / +46 lines)
Lines 15-27 Link Here
15
# with Koha; if not, see <http://www.gnu.org/licenses>.
15
# with Koha; if not, see <http://www.gnu.org/licenses>.
16
16
17
use Modern::Perl;
17
use Modern::Perl;
18
use Test::More tests => 5;
18
use Test::More tests => 3;
19
19
20
use C4::Context;
20
use C4::Context;
21
use C4::Log;
21
use C4::Log;
22
use C4::Auth qw/checkpw/;
22
use C4::Auth qw/checkpw/;
23
use Koha::Database;
23
use Koha::Database;
24
use Koha::DateUtils;
24
use Koha::DateUtils;
25
use Koha::ActionLogs;
25
26
26
use t::lib::Mocks qw/mock_preference/; # to mock CronjobLog
27
use t::lib::Mocks qw/mock_preference/; # to mock CronjobLog
27
use t::lib::TestBuilder;
28
use t::lib::TestBuilder;
Lines 32-38 $schema->storage->txn_begin; Link Here
32
our $dbh = C4::Context->dbh;
33
our $dbh = C4::Context->dbh;
33
34
34
subtest 'Existing tests' => sub {
35
subtest 'Existing tests' => sub {
35
    plan tests => 6;
36
    plan tests => 3;
36
37
37
    my $success;
38
    my $success;
38
    eval {
39
    eval {
Lines 45-77 subtest 'Existing tests' => sub { Link Here
45
    };
46
    };
46
    ok($success, "logaction seemed to work");
47
    ok($success, "logaction seemed to work");
47
48
48
    eval {
49
        # FIXME: US formatted date hardcoded into test for now
50
        $success = scalar(@{GetLogs("","","",undef,undef,"","")});
51
    } or do {
52
        diag($@);
53
        $success = 0;
54
    };
55
    ok($success, "GetLogs returns results for an open search");
56
57
    eval {
58
        # FIXME: US formatted date hardcoded into test for now
59
        my $date = output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } );
60
        $success = scalar(@{GetLogs( $date, $date, "", undef, undef, "", "") } );
61
    } or do {
62
        diag($@);
63
        $success = 0;
64
    };
65
    ok($success, "GetLogs accepts dates in an All-matching search");
66
67
    eval {
68
        $success = scalar(@{GetLogs("","","",["MEMBERS"],["MODIFY"],1,"")});
69
    } or do {
70
        diag($@);
71
        $success = 0;
72
    };
73
    ok($success, "GetLogs seemed to find ".$success." like our test record in a tighter search");
74
75
    # We want numbers to be the same between runs.
49
    # We want numbers to be the same between runs.
76
    $dbh->do("DELETE FROM action_logs;");
50
    $dbh->do("DELETE FROM action_logs;");
77
51
Lines 86-107 subtest 'Existing tests' => sub { Link Here
86
    is($cronJobCount,1,"Cronjob logged as expected.");
60
    is($cronJobCount,1,"Cronjob logged as expected.");
87
};
61
};
88
62
89
subtest "GetLogs should return all logs if dates are not set" => sub {
90
    plan tests => 2;
91
    my $today = dt_from_string->add(minutes => -1);
92
    my $yesterday = dt_from_string->add( days => -1 );
93
    $dbh->do(q|
94
        INSERT INTO action_logs (timestamp, user, module, action, object, info)
95
        VALUES
96
        (?, 42, 'CATALOGUING', 'MODIFY', 4242, 'Record 42 has been modified by patron 4242 yesterday'),
97
        (?, 43, 'CATALOGUING', 'MODIFY', 4242, 'Record 43 has been modified by patron 4242 today')
98
    |, undef, output_pref({dt =>$yesterday, dateformat => 'iso'}), output_pref({dt => $today, dateformat => 'iso'}));
99
    my $logs = GetLogs( undef, undef, undef, ['CATALOGUING'], ['MODIFY'], 4242 );
100
    is( scalar(@$logs), 2, 'GetLogs should return all logs regardless the dates' );
101
    $logs = GetLogs( output_pref($today), undef, undef, ['CATALOGUING'], ['MODIFY'], 4242 );
102
    is( scalar(@$logs), 1, 'GetLogs should return the logs for today' );
103
};
104
105
subtest 'logaction(): interface is correctly logged' => sub {
63
subtest 'logaction(): interface is correctly logged' => sub {
106
64
107
    plan tests => 4;
65
    plan tests => 4;
Lines 110-165 subtest 'logaction(): interface is correctly logged' => sub { Link Here
110
    $dbh->do("DELETE FROM action_logs;");
68
    $dbh->do("DELETE FROM action_logs;");
111
    C4::Context->interface( 'commandline' );
69
    C4::Context->interface( 'commandline' );
112
    logaction( "MEMBERS", "MODIFY", 1, "test operation");
70
    logaction( "MEMBERS", "MODIFY", 1, "test operation");
113
    my $logs = GetLogs();
71
    my $log = Koha::ActionLogs->search->next;
114
    is( @{$logs}[0]->{ interface }, 'commandline', 'Interface correctly deduced (commandline)');
72
    is( $log->interface, 'commandline', 'Interface correctly deduced (commandline)');
115
73
116
    # No interface passed, using C4::Context->interface
74
    # No interface passed, using C4::Context->interface
117
    $dbh->do("DELETE FROM action_logs;");
75
    $dbh->do("DELETE FROM action_logs;");
118
    C4::Context->interface( 'opac' );
76
    C4::Context->interface( 'opac' );
119
    logaction( "MEMBERS", "MODIFY", 1, "test operation");
77
    logaction( "MEMBERS", "MODIFY", 1, "test operation");
120
    $logs = GetLogs();
78
    $log = Koha::ActionLogs->search->next;
121
    is( @{$logs}[0]->{ interface }, 'opac', 'Interface correctly deduced (opac)');
79
    is( $log->interface, 'opac', 'Interface correctly deduced (opac)');
122
80
123
    # Explicit interfaces
81
    # Explicit interfaces
124
    $dbh->do("DELETE FROM action_logs;");
82
    $dbh->do("DELETE FROM action_logs;");
125
    C4::Context->interface( 'intranet' );
83
    C4::Context->interface( 'intranet' );
126
    logaction( "MEMBERS", "MODIFY", 1, 'test info', 'intranet');
84
    logaction( "MEMBERS", "MODIFY", 1, 'test info', 'intranet');
127
    $logs = GetLogs();
85
    $log = Koha::ActionLogs->search->next;
128
    is( @{$logs}[0]->{ interface }, 'intranet', 'Passed interface is respected (intranet)');
86
    is( $log->interface, 'intranet', 'Passed interface is respected (intranet)');
129
87
130
    # Explicit interfaces
88
    # Explicit interfaces
131
    $dbh->do("DELETE FROM action_logs;");
89
    $dbh->do("DELETE FROM action_logs;");
132
    C4::Context->interface( 'sip' );
90
    C4::Context->interface( 'sip' );
133
    logaction( "MEMBERS", "MODIFY", 1, 'test info', 'sip');
91
    logaction( "MEMBERS", "MODIFY", 1, 'test info', 'sip');
134
    $logs = GetLogs();
92
    $log = Koha::ActionLogs->search->next;
135
    is( @{$logs}[0]->{ interface }, 'sip', 'Passed interface is respected (sip)');
93
    is( $log->interface, 'sip', 'Passed interface is respected (sip)');
136
};
137
138
subtest 'GetLogs() respects interface filters' => sub {
139
140
    plan tests => 5;
141
142
    $dbh->do("DELETE FROM action_logs;");
143
144
    logaction( 'MEMBERS', 'MODIFY', 1, 'opac info',        'opac');
145
    logaction( 'MEMBERS', 'MODIFY', 1, 'sip info',         'sip');
146
    logaction( 'MEMBERS', 'MODIFY', 1, 'intranet info',    'intranet');
147
    logaction( 'MEMBERS', 'MODIFY', 1, 'commandline info', 'commandline');
148
149
    my $logs = scalar @{ GetLogs() };
150
    is( $logs, 4, 'If no filter on interfaces is passed, all logs are returned');
151
152
    $logs = GetLogs(undef,undef,undef,undef,undef,undef,undef,['opac']);
153
    is( @{$logs}[0]->{ interface }, 'opac', 'Interface correctly filtered (opac)');
154
155
    $logs = GetLogs(undef,undef,undef,undef,undef,undef,undef,['sip']);
156
    is( @{$logs}[0]->{ interface }, 'sip', 'Interface correctly filtered (sip)');
157
158
    $logs = GetLogs(undef,undef,undef,undef,undef,undef,undef,['intranet']);
159
    is( @{$logs}[0]->{ interface }, 'intranet', 'Interface correctly filtered (intranet)');
160
161
    $logs = GetLogs(undef,undef,undef,undef,undef,undef,undef,['commandline']);
162
    is( @{$logs}[0]->{ interface }, 'commandline', 'Interface correctly filtered (commandline)');
163
};
94
};
164
95
165
subtest 'GDPR logging' => sub {
96
subtest 'GDPR logging' => sub {
Lines 170-177 subtest 'GDPR logging' => sub { Link Here
170
101
171
    t::lib::Mocks::mock_userenv({ patron => $patron });
102
    t::lib::Mocks::mock_userenv({ patron => $patron });
172
    logaction( 'AUTH', 'FAILURE', $patron->id, '', 'opac' );
103
    logaction( 'AUTH', 'FAILURE', $patron->id, '', 'opac' );
173
    my $logs = GetLogs( undef, undef, $patron->id, ['AUTH'], ['FAILURE'], $patron->id );
104
    my $logs = Koha::ActionLogs->search(
174
    is( @$logs, 1, 'We should find one auth failure' );
105
        {
106
            user   => $patron->id,
107
            module => 'AUTH',
108
            action => 'FAILURE',
109
            object => $patron->id,
110
        }
111
    );
112
    is( $logs->count, 1, 'We should find one auth failure' );
175
113
176
    t::lib::Mocks::mock_preference('AuthFailureLog', 1);
114
    t::lib::Mocks::mock_preference('AuthFailureLog', 1);
177
    my $strong_password = 'N0tStr0ngAnyM0reN0w:)';
115
    my $strong_password = 'N0tStr0ngAnyM0reN0w:)';
Lines 179-196 subtest 'GDPR logging' => sub { Link Here
179
    my @ret = checkpw( $dbh, $patron->userid, 'WrongPassword', undef, undef, 1);
117
    my @ret = checkpw( $dbh, $patron->userid, 'WrongPassword', undef, undef, 1);
180
    is( $ret[0], 0, 'Authentication failed' );
118
    is( $ret[0], 0, 'Authentication failed' );
181
    # Look for auth failure but NOT on patron id, pass userid in info parameter
119
    # Look for auth failure but NOT on patron id, pass userid in info parameter
182
    $logs = GetLogs( undef, undef, 0, ['AUTH'], ['FAILURE'], undef, $patron->userid );
120
    $logs = Koha::ActionLogs->search(
183
    is( @$logs, 1, 'We should find one auth failure with this userid' );
121
        {
122
            module => 'AUTH',
123
            action => 'FAILURE',
124
            info   => { -like => '%'.$patron->userid.'%' },
125
        }
126
    );
127
    is( $logs->count, 1, 'We should find one auth failure with this userid' );
184
    t::lib::Mocks::mock_preference('AuthFailureLog', 0);
128
    t::lib::Mocks::mock_preference('AuthFailureLog', 0);
185
    @ret = checkpw( $dbh, $patron->userid, 'WrongPassword', undef, undef, 1);
129
    @ret = checkpw( $dbh, $patron->userid, 'WrongPassword', undef, undef, 1);
186
    $logs = GetLogs( undef, undef, 0, ['AUTH'], ['FAILURE'], undef, $patron->userid );
130
    $logs = Koha::ActionLogs->search(
187
    is( @$logs, 1, 'Still only one failure with this userid' );
131
        {
132
            module => 'AUTH',
133
            action => 'FAILURE',
134
            info   => { -like => '%'.$patron->userid.'%' },
135
        }
136
    );
137
    is( $logs->count, 1, 'Still only one failure with this userid' );
188
    t::lib::Mocks::mock_preference('AuthSuccessLog', 1);
138
    t::lib::Mocks::mock_preference('AuthSuccessLog', 1);
189
    @ret = checkpw( $dbh, $patron->userid, $strong_password, undef, undef, 1);
139
    @ret = checkpw( $dbh, $patron->userid, $strong_password, undef, undef, 1);
190
    is( $ret[0], 1, 'Authentication succeeded' );
140
    is( $ret[0], 1, 'Authentication succeeded' );
191
    # Now we can look for patron id
141
    # Now we can look for patron id
192
    $logs = GetLogs( undef, undef, $patron->id, ['AUTH'], ['SUCCESS'], $patron->id );
142
    $logs = Koha::ActionLogs->search(
193
    is( @$logs, 1, 'We expect only one auth success line for this patron' );
143
        {
144
            user   => $patron->id,
145
            module => 'AUTH',
146
            action => 'SUCCESS',
147
            object => $patron->id,
148
        }
149
    );
150
151
    is( $logs->count, 1, 'We expect only one auth success line for this patron' );
194
};
152
};
195
153
196
$schema->storage->txn_rollback;
154
$schema->storage->txn_rollback;
197
- 

Return to bug 23632