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

(-)a/C4/Log.pm (-10 / +17 lines)
Lines 140-157 sub logaction { Link Here
140
    $diff //= encode_json( diff( $original, $updated, noU => 1 ) )
140
    $diff //= encode_json( diff( $original, $updated, noU => 1 ) )
141
        if $original && ref $updated eq 'HASH';
141
        if $original && ref $updated eq 'HASH';
142
142
143
    # Get the IP address from the environment if IP logging is enabled
144
    my $ip_address = undef;
145
    if ( C4::Context->preference('ActionLogsEnableIPLogging') ) {
146
        $ip_address = $ENV{REMOTE_ADDR} || undef;
147
    }
148
143
    Koha::ActionLog->new(
149
    Koha::ActionLog->new(
144
        {
150
        {
145
            timestamp => \'NOW()',
151
            timestamp  => \'NOW()',
146
            user      => $usernumber,
152
            user       => $usernumber,
147
            module    => $modulename,
153
            module     => $modulename,
148
            action    => $actionname,
154
            action     => $actionname,
149
            object    => $objectnumber,
155
            object     => $objectnumber,
150
            info      => $infos,
156
            info       => $infos,
151
            interface => $interface,
157
            interface  => $interface,
152
            script    => $script,
158
            script     => $script,
153
            trace     => $trace,
159
            trace      => $trace,
154
            diff      => $diff,
160
            diff       => $diff,
161
            ip_address => $ip_address,
155
        }
162
        }
156
    )->store();
163
    )->store();
157
164
(-)a/Koha/Schema/Result/ActionLog.pm (+10 lines)
Lines 106-111 An optional stack trace enabled by ActionLogsTraceDepth Link Here
106
106
107
Stores a diff of the changed object
107
Stores a diff of the changed object
108
108
109
=head2 ip_address
110
111
  data_type: 'varchar'
112
  is_nullable: 1
113
  size: 45
114
115
IP address of the client that performed the action
116
109
=cut
117
=cut
110
118
111
__PACKAGE__->add_columns(
119
__PACKAGE__->add_columns(
Lines 136-141 __PACKAGE__->add_columns( Link Here
136
  { data_type => "text", is_nullable => 1 },
144
  { data_type => "text", is_nullable => 1 },
137
  "diff",
145
  "diff",
138
  { data_type => "longtext", is_nullable => 1 },
146
  { data_type => "longtext", is_nullable => 1 },
147
  "ip_address",
148
  { data_type => "varchar", is_nullable => 1, size => 45 },
139
);
149
);
140
150
141
=head1 PRIMARY KEY
151
=head1 PRIMARY KEY
(-)a/installer/data/mysql/atomicupdate/bug_41310-add_ip_address_to_action_logs.pl (+26 lines)
Line 0 Link Here
1
use Modern::Perl;
2
use Koha::Installer::Output qw(say_warning say_success say_info);
3
4
return {
5
    bug_number  => "41310",
6
    description => "Add remote_ip (REMOTE_ADDR) to action_logs table to improve security auditing",
7
    up          => sub {
8
        my ($args) = @_;
9
        my ( $dbh, $out ) = @$args{qw(dbh out)};
10
11
        if ( !column_exists( 'action_logs', 'ip_address' ) ) {
12
            $dbh->do(q{
13
                ALTER TABLE action_logs
14
                ADD COLUMN `ip_address` varchar(45) DEFAULT NULL COMMENT 'IP address of the client that performed the action' AFTER `diff`
15
            });
16
            say_success( $out, "Added column 'action_logs.ip_address'" );
17
        } else {
18
            say_info( $out, "Column 'action_logs.ip_address' already exists!" );
19
        }
20
21
        $dbh->do(q{
22
            INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) 
23
            VALUES ('ActionLogsEnableIPLogging', '0', NULL, 'If enabled, store the IP address of the client in action logs for security auditing.', 'YesNo')});
24
        say_success( $out, "Added new system preference 'ActionLogsEnableIPLogging'" );
25
    },
26
};
(-)a/installer/data/mysql/kohastructure.sql (+1 lines)
Lines 180-185 CREATE TABLE `action_logs` ( Link Here
180
  `script` varchar(255) DEFAULT NULL COMMENT 'the name of the cron script that caused this change',
180
  `script` varchar(255) DEFAULT NULL COMMENT 'the name of the cron script that caused this change',
181
  `trace` text DEFAULT NULL COMMENT 'An optional stack trace enabled by ActionLogsTraceDepth',
181
  `trace` text DEFAULT NULL COMMENT 'An optional stack trace enabled by ActionLogsTraceDepth',
182
  `diff` longtext DEFAULT NULL COMMENT 'Stores a diff of the changed object',
182
  `diff` longtext DEFAULT NULL COMMENT 'Stores a diff of the changed object',
183
  `ip_address` varchar(45) DEFAULT NULL COMMENT 'IP address of the client that performed the action',
183
  PRIMARY KEY (`action_id`),
184
  PRIMARY KEY (`action_id`),
184
  KEY `timestamp_idx` (`timestamp`),
185
  KEY `timestamp_idx` (`timestamp`),
185
  KEY `user_idx` (`user`),
186
  KEY `user_idx` (`user`),
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+1 lines)
Lines 12-17 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
12
('AcquisitionsDefaultReplyTo', '', NULL, 'Default email address used as reply-to for notices sent by the acquisitions module.', 'Free'),
12
('AcquisitionsDefaultReplyTo', '', NULL, 'Default email address used as reply-to for notices sent by the acquisitions module.', 'Free'),
13
('AcqViewBaskets','user','user|branch|all','Define which baskets a user is allowed to view: their own only, any within their branch, or all','Choice'),
13
('AcqViewBaskets','user','user|branch|all','Define which baskets a user is allowed to view: their own only, any within their branch, or all','Choice'),
14
('AcqWarnOnDuplicateInvoice','0',NULL,'Warn librarians when they try to create a duplicate invoice','YesNo'),
14
('AcqWarnOnDuplicateInvoice','0',NULL,'Warn librarians when they try to create a duplicate invoice','YesNo'),
15
('ActionLogsEnableIPLogging', '0', NULL, 'If enabled, store the IP address of the client in action logs for security auditing.', 'YesNo'),
15
('ActionLogsTraceDepth', '0', NULL, 'Sets the maximum depth of the action logs stack trace', 'Integer'),
16
('ActionLogsTraceDepth', '0', NULL, 'Sets the maximum depth of the action logs stack trace', 'Integer'),
16
('AdditionalContentLog','0',NULL,'If ON, log OPAC news changes','YesNo'),
17
('AdditionalContentLog','0',NULL,'If ON, log OPAC news changes','YesNo'),
17
('AdditionalContentsEditor','tinymce','tinymce|codemirror','Choose tool for editing News.', 'Choice'),
18
('AdditionalContentsEditor','tinymce','tinymce|codemirror','Choose tool for editing News.', 'Choice'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/logs.pref (+7 lines)
Lines 140-145 Logging: Link Here
140
            - item transfers.
140
            - item transfers.
141
141
142
    Debugging:
142
    Debugging:
143
        -
144
            - "When logging actions, store the IP address of the client"
145
            - pref: "ActionLogsEnableIPLogging"
146
              choices:
147
                  1: "Yes"
148
                  0: "No (recommended for GDPR compliance)"
149
            - "Note: IP addresses may be considered personal data under GDPR."
143
        -
150
        -
144
            - "When logging actions, store a stack trace that goes at most"
151
            - "When logging actions, store a stack trace that goes at most"
145
            - pref: "ActionLogsTraceDepth"
152
            - pref: "ActionLogsTraceDepth"
(-)a/t/db_dependent/TestIpLogging.t (-1 / +58 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Test script to verify IP address logging in action_logs
4
# This script tests that the IP address is properly stored in action_logs
5
# based on the ActionLogsEnableIPLogging system preference
6
7
use Modern::Perl;
8
use Test::More tests => 10;
9
10
use C4::Context;
11
use C4::Log qw(logaction);
12
use Koha::Database;
13
use Koha::ActionLogs;
14
use t::lib::TestBuilder;
15
use t::lib::Mocks;
16
17
my $schema = Koha::Database->new->schema;
18
$schema->storage->txn_begin;
19
20
my $builder = t::lib::TestBuilder->new;
21
22
# Mock REMOTE_ADDR
23
$ENV{REMOTE_ADDR} = '192.168.1.100';
24
25
# Clear any existing action logs for this test
26
Koha::ActionLogs->search->delete;
27
28
# Perform a test action log
29
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
30
31
# Test 1: IP logging DISABLED (default)
32
t::lib::Mocks::mock_preference( 'ActionLogsEnableIPLogging', 0 );
33
34
logaction( "TESTING", "ADD_DISABLED", $patron->borrowernumber, "Test IP logging disabled", "intranet" );
35
36
my $action_log_disabled = Koha::ActionLogs->search( { module => 'TESTING', action => 'ADD_DISABLED' } )->next;
37
ok( $action_log_disabled, "Action log was created with IP logging disabled" );
38
is( $action_log_disabled->module,     "TESTING",                  "Module is correct when disabled" );
39
is( $action_log_disabled->action,     "ADD_DISABLED",             "Action is correct when disabled" );
40
is( $action_log_disabled->info,       "Test IP logging disabled", "Info is correct when disabled" );
41
is( $action_log_disabled->ip_address, undef,                      "IP address is NOT logged when disabled" );
42
43
# Test 2: IP logging ENABLED
44
t::lib::Mocks::mock_preference( 'ActionLogsEnableIPLogging', 1 );
45
46
logaction( "TESTING", "ADD_ENABLED", $patron->borrowernumber, "Test IP logging enabled", "intranet" );
47
48
my $action_log_enabled = Koha::ActionLogs->search( { module => 'TESTING', action => 'ADD_ENABLED' } )->next;
49
ok( $action_log_enabled, "Action log was created with IP logging enabled" );
50
is( $action_log_enabled->module,     "TESTING",                 "Module is correct when enabled" );
51
is( $action_log_enabled->action,     "ADD_ENABLED",             "Action is correct when enabled" );
52
is( $action_log_enabled->info,       "Test IP logging enabled", "Info is correct when enabled" );
53
is( $action_log_enabled->ip_address, "192.168.1.100",           "IP address is logged when enabled" );
54
55
# Clean up
56
$schema->storage->txn_rollback;
57
58
done_testing();

Return to bug 41310