Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\task;
18
 
19
use core\http_client;
20
use moodle_exception;
21
use PharData;
22
 
23
/**
24
 * Simple task to update the GeoIP database file.
25
 *
26
 * @package     core
27
 * @author      Trisha Milan <trishamilan@catalyst-au.net>
28
 * @copyright   Monash University 2024
29
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class update_geoip2file_task extends scheduled_task {
32
 
33
    /**
34
     * Get a descriptive name for this task (shown to admins).
35
     *
36
     * @return string
37
     */
38
    public function get_name(): string {
39
        return get_string('taskupdategeoip2file', 'admin');
40
    }
41
 
42
    /**
43
     * Execute the task to update the GeoIP2 database file.
44
     *
45
     * @throws \GuzzleHttp\Exception\GuzzleException
46
     * @throws moodle_exception
47
     */
48
    public function execute(): void {
49
        global $CFG;
50
 
51
        if (!$CFG->geoipmaxmindaccid || !$CFG->geoipmaxmindlicensekey) {
52
            mtrace("MaxMind account information is incomplete. Please configure the account ID and license key.");
53
            return;
54
        }
55
 
56
        // Setup base directory path and permissions.
57
        $geoip2file = $CFG->geoip2file;
58
        $geoipdirectory = dirname($geoip2file);
59
        if (!check_dir_exists($geoipdirectory) && !mkdir($geoipdirectory, $CFG->directorypermissions, true)) {
60
            throw new moodle_exception("Cannot create output directory $geoipdirectory");
61
        }
62
 
63
        $geoippermalink = 'https://download.maxmind.com/geoip/databases/' . $CFG->geoipdbedition . '/download';
64
 
65
        $client = new http_client(['auth' => [$CFG->geoipmaxmindaccid, $CFG->geoipmaxmindlicensekey]]);
66
        $response = $client->head($geoippermalink, ['query' => ['suffix' => 'tar.gz']]);
67
        $headers = $response->getHeaders();
68
        $lastmodified = strtotime($headers['Last-Modified'][0]);
69
        if (!$this->is_update_needed($geoip2file, $lastmodified)) {
70
            mtrace("No update needed. The GeoIP database is up to date.");
71
            return;
72
        }
73
 
74
        // Define path for downloading the GeoIP2 archive.
75
        $archivefilename = 'GeoIP-City.tar.gz';
76
        $tempdirectory = make_request_directory(true);
77
        $geoipdownloadpath = $tempdirectory . '/' . $archivefilename;
78
 
79
        mtrace("Downloading $CFG->geoipdbedition database from MaxMind......");
80
        $response = $client->request('GET', $geoippermalink, [
81
            'query' => ['suffix' => 'tar.gz'],
82
            'sink' => $geoipdownloadpath,
83
        ]);
84
        if ($response->getStatusCode() != 200) {
85
            throw new moodle_exception("Error downloading file.");
86
        }
87
 
88
        mtrace("File downloaded successfully to $geoipdownloadpath");
89
        mtrace("Verifying checksum......");
90
 
91
        // Get the latest checksum from MaxMind.
92
        $checksumcontent = $client->get($geoippermalink, ['query' => ['suffix' => 'tar.gz.sha256']])->getBody()->getContents();
93
        list($checksum) = explode(' ', $checksumcontent);
94
        if (!$this->verify_checksum($checksum, $geoipdownloadpath)) {
95
            throw new moodle_exception("Checksum verification failed.");
96
        }
97
 
98
        mtrace("Checksum verified successfully.");
99
        if ($this->update_geoip2file($geoipdownloadpath, $tempdirectory, $geoip2file)) {
100
            // Store the last seen timestamp.
101
            set_config('geoip_last_seen_timestamp', $lastmodified);
102
            mtrace("GeoIP database update successful!");
103
        } else {
104
            throw new moodle_exception("GeoIP database update failed.");
105
        }
106
    }
107
 
108
    /**
109
     * Determines if an update is needed for the GeoIP2 file based on the last modified date.
110
     *
111
     * @param string $geoip2file The path to the GeoIP2 file that needs to be checked for updates.
112
     * @param string $lastmodified The last modified date to be compared against the stored last seen timestamp.
113
     * @return bool
114
     */
115
    private function is_update_needed(string $geoip2file, string $lastmodified): bool {
116
        return !file_exists($geoip2file) || $lastmodified !== get_config('core', 'geoip_last_seen_timestamp');
117
    }
118
 
119
    /**
120
     * Verify the checksum of the downloaded file against an expected checksum.
121
     *
122
     * @param string $expectedchecksum The checksum expected for the file.
123
     * @param string $geoipdownloadpath The path where the downloaded geoip archive is located.
124
     * @return bool Returns true if the checksums match, returns false otherwise.
125
     */
126
    private function verify_checksum(string $expectedchecksum, string $geoipdownloadpath): bool {
127
        $actualchecksum = hash_file('sha256', $geoipdownloadpath);
128
        return $expectedchecksum === $actualchecksum;
129
    }
130
 
131
    /**
132
     * Extract the archive and update the GeoIP2 database file.
133
     *
134
     * @param string $archivepath The path to the archive file that needs to be extracted.
135
     * @param string $targetdirectory Directory where the archive contents will be extracted.
136
     * @param string $geoip2file The path to move the extracted GeoIP2 file.
137
     * @return bool Returns true if the file was successfully extracted and moved to the specified location,
138
     *              false if any part of the process fails.
139
     */
140
    private function update_geoip2file(string $archivepath, string $targetdirectory, string $geoip2file): bool {
141
        $archive = new PharData($archivepath);
142
        $archivename = $archive->getFilename();
143
 
144
        mtrace("Extracting file......");
145
        $archive->extractTo($targetdirectory);
146
        $sourcefolder = $targetdirectory . '/' . $archivename;
147
 
148
        // Find the mmdb file.
149
        $mmdbfiles = glob($sourcefolder . '/*.mmdb');
150
        if (count($mmdbfiles) > 1) {
151
            throw new moodle_exception("Multiple .mmdb files found in the extracted folder.");
152
        } else if (count($mmdbfiles) === 0) {
153
            throw new moodle_exception("GeoIP file does not exist.");
154
        }
155
 
156
        // Backup existing GeoIP file before attempting to update.
157
        $geoip2filename = basename($geoip2file);
158
        $backuppath = $targetdirectory . '/' . 'backup_' . $geoip2filename;
159
        if (file_exists($geoip2file)) {
160
            if (!rename($geoip2file, $backuppath)) {
161
                mtrace("Failed to create a backup of the existing GeoIP database.");
162
            }
163
            mtrace("Temporary backup of existing GeoIP file has been created.");
164
        }
165
 
166
        mtrace("Moving {$mmdbfiles[0]} into $geoip2file");
167
        if (!copy($mmdbfiles[0], $geoip2file)) {
168
            mtrace("Failed to update $geoip2filename.");
169
            // Attempt to restore the original file from the backup.
170
            if (file_exists($backuppath)) {
171
                mtrace("Attempting to restore from backup.");
172
                if (!copy($backuppath, $geoip2file)) {
173
                    throw new moodle_exception("Failed to restore the GeoIP database from backup.");
174
                } else {
175
                    mtrace("The GeoIP database has been restored from the backup successfully.");
176
                }
177
            }
178
            return false;
179
        }
180
        mtrace("$geoip2filename updated successfully.");
181
        return true;
182
    }
183
}