Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
/**
18
 * Check the presence of public paths via curl.
19
 *
20
 * @package    core
21
 * @category   check
22
 * @copyright  2020 Brendan Heywood <brendan@catalyst-au.net>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core\check\environment;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
use core\check\check;
31
use core\check\result;
32
 
33
/**
34
 * Check the public access of various paths.
35
 *
36
 * @copyright  2020 Brendan Heywood <brendan@catalyst-au.net>
37
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38
 */
39
class publicpaths extends check {
40
 
41
    /**
42
     * Get the short check name
43
     *
44
     * @return string
45
     */
46
    public function get_name(): string {
47
        return get_string('check_publicpaths_name', 'report_security');
48
    }
49
 
50
    /**
51
     * Returns a list of test urls and metadata.
52
     */
53
    public function get_pathsets() {
54
        global $CFG;
55
 
56
        // The intention here is that each pattern is a simple regex such that
57
        // in future perhaps the various webserver config could be generated as more
58
        // pattens are added to these checks.
59
        return [
60
            [
61
                'pattern'   => '/vendor/',
62
                '404'       => [
63
                    'vendor/',
64
                    'vendor/bin/behat',
65
                ],
66
                'details'   => get_string('check_vendordir_details', 'report_security', ['path' => $CFG->dirroot.'/vendor']),
67
                'summary'   => get_string('check_vendordir_info', 'report_security'),
68
            ],
69
            [
70
                'pattern'   => '/node_modules/',
71
                '404'       => [
72
                    'node_modules/',
73
                    'node_modules/cli/cli.js',
74
                ],
75
                'summary'   => get_string('check_nodemodules_info', 'report_security'),
76
                'details'   => get_string('check_nodemodules_details', 'report_security',
77
                        ['path' => $CFG->dirroot . '/node_modules']),
78
            ],
79
            [
80
                'pattern'   => '^\..*',
81
                '404'       => [
82
                    '.git/',
83
                    '.git/HEAD',
84
                    '.github/FUNDING.yml',
85
                    '.stylelintrc',
1441 ariadna 86
                    '.upgradenotes/',
1 efrain 87
                ],
88
            ],
89
            [
90
                'pattern'   => 'composer.json',
91
                '404'       => [
92
                    'composer.json',
93
                ],
94
            ],
95
            [
96
                'pattern'   => '.lock',
97
                '404'       => [
98
                    'composer.lock',
99
                ],
100
            ],
101
            [
102
                'pattern'   => 'environment.xml',
103
                '404'       => [
104
                    'admin/environment.xml',
105
                ],
106
            ],
107
            [
108
                'pattern'   => '',
109
                '404'       => [
110
                    'doesnotexist', // Just to make sure that real 404s are still 404s.
111
                ],
112
                'summary'   => '',
113
            ],
114
            [
115
                'pattern'   => '',
116
                '404'       => [
117
                    'lib/classes/',
118
                ],
119
                'summary'   => get_string('check_dirindex_info', 'report_security'),
120
            ],
121
            [
122
                'pattern'   => 'db/install.xml',
123
                '404'       => [
124
                    'lib/db/install.xml',
125
                    'mod/assign/db/install.xml',
126
                ],
127
            ],
128
            [
129
                'pattern'   => 'readme.txt',
130
                '404'       => [
131
                    'lib/scssphp/readme_moodle.txt',
132
                    'mod/resource/readme.txt',
133
                ],
134
            ],
135
            [
136
                'pattern'   => 'README',
137
                '404'       => [
138
                    'mod/README.txt',
139
                    'mod/book/README.md',
140
                ],
141
            ],
142
            [
1441 ariadna 143
                'pattern'   => '\/(upgrade\.txt|UPGRADING\.md|UPGRADING\-CURRENT\.md)',
1 efrain 144
                '404'       => [
145
                    'auth/manual/upgrade.txt',
146
                    'lib/upgrade.txt',
1441 ariadna 147
                    'UPGRADING.md',
148
                    'UPGRADING-CURRENT.md',
149
                    'reportbuilder/UPGRADING.md',
1 efrain 150
                ],
1441 ariadna 151
                'summary' => get_string('check_upgradefile_info', 'report_security'),
1 efrain 152
            ],
153
            [
154
                'pattern'   => 'phpunit.xml',
155
                '404'       => ['phpunit.xml.dist'],
156
            ],
157
            [
158
                'pattern'   => '/fixtures/',
159
                '404'       => [
160
                    'privacy/tests/fixtures/logo.png',
161
                    'enrol/lti/tests/fixtures/input.xml',
162
                ],
163
            ],
164
            [
165
                'pattern'   => '/behat/',
166
                '404'       => ['blog/tests/behat/delete.feature'],
167
            ],
168
        ];
169
    }
170
 
171
    /**
172
     * Return result
173
     * @return result
174
     */
175
    public function get_result(): result {
176
        global $CFG, $OUTPUT;
177
 
178
        $status = result::OK;
179
        $details = '';
180
        $summary = get_string('check_publicpaths_ok', 'report_security');
181
        $errors = [];
182
 
183
        $c = new \curl();
184
        $paths = $this->get_pathsets();
185
 
186
        $table = new \html_table();
187
        $table->align = ['center', 'right', 'left'];
188
        $table->size = ['1%', '1%', '1%', '1%', '1%', '99%'];
189
        $table->head = [
190
            get_string('status'),
191
            get_string('checkexpected'),
192
            get_string('checkactual'),
193
            get_string('url'),
194
            get_string('category'),
195
            get_string('details'),
196
        ];
197
        $table->attributes['class'] = 'flexible generaltable generalbox table-sm';
198
        $table->data = [];
199
 
200
        // Used to track duplicated errors.
201
        $lastdetail = '-';
202
 
203
        $curl = new \curl();
204
        $requests = [];
205
 
206
        // Build up a list of all url so we can load them in parallel.
207
        foreach ($paths as $path) {
208
            foreach (['200', '404'] as $expected) {
209
                if (!isset($path[$expected])) {
210
                    continue;
211
                }
212
                foreach ($path[$expected] as $test) {
213
                    $requests[] = [
214
                        'nobody'    => true,
215
                        'header'    => 1,
216
                        'url'       => $CFG->wwwroot . '/' . $test,
217
                        'returntransfer' => true,
218
                    ];
219
                }
220
            }
221
        }
222
 
223
        $headers = $curl->download($requests);
224
 
225
        foreach ($paths as $path) {
226
            foreach (['200', '404'] as $expected) {
227
                if (!isset($path[$expected])) {
228
                    continue;
229
                }
230
                foreach ($path[$expected] as $test) {
231
                    $rowsummary = '';
232
                    $rowdetail = '';
233
 
234
                    $url = $CFG->wwwroot . '/' . $test;
235
 
236
                    // Parse the HTTP header to get the 200 / 404 code.
237
                    $header = array_shift($headers);
238
                    $actual = strtok($header, "\n");
239
                    $actual = strtok($actual, " ");
240
                    $actual = strtok(" ");
241
 
242
                    if ($actual != $expected) {
243
                        if (isset($path['summary'])) {
244
                            $rowsummary = $path['summary'];
245
                        } else {
246
                            $rowsummary = get_string('check_publicpaths_generic',
247
                                'report_security', $path['pattern']);
248
                        }
249
 
250
                        // Special case where a 404 is ideal but a 403 is ok too.
251
                        if ($actual == 403) {
252
                            $result = new result(result::INFO, '', '');
253
                            $rowsummary .= get_string('check_publicpaths_403', 'report_security');
254
                        } else {
255
                            $result = new result(result::ERROR, '', '');
256
                            $status = result::ERROR;
257
                            $summary = get_string('check_publicpaths_warning', 'report_security');
258
                        }
259
 
260
                        $rowdetail = isset($path['details']) ? $path['details'] : $rowsummary;
261
 
262
                        if (empty($errors[$path['pattern']])) {
263
                            $summary .= '<li>' . $rowsummary . '</li>';
264
                            $errors[$path['pattern']] = 1;
265
                        }
266
 
267
                    } else {
268
                        $result = new result(result::OK, '', '');
269
                    }
270
 
271
                    $table->data[] = [
272
                        $OUTPUT->check_result($result),
273
                        $expected,
274
                        $actual,
275
                        $OUTPUT->action_link($url, $test, null, ['target' => '_blank']),
276
                        "<pre>{$path['pattern']}</pre>",
277
                    ];
278
 
279
                    // Merge duplicate details to display a nicer table.
280
                    if ($rowdetail == $lastdetail) {
281
                        $duplicates++;
282
                    } else {
283
                        $duplicates = 1;
284
                    }
285
                    $detailcell = new \html_table_cell($rowdetail);
286
                    $detailcell->rowspan = $duplicates;
287
                    $rows = count($table->data);
288
                    $table->data[$rows - $duplicates][5] = $detailcell;
289
                    $lastdetail = $rowdetail;
290
                }
291
            }
292
        }
293
 
294
        $details .= \html_writer::table($table);
295
 
296
        return new result($status, $summary, $details);
297
    }
298
 
299
    /**
300
     * Link to the dev docs for more info.
301
     *
302
     * @return \action_link|null
303
     */
304
    public function get_action_link(): ?\action_link {
305
        return new \action_link(
306
            new \moodle_url(\get_docs_url('Installing_Moodle#Set_up_your_server')),
307
            get_string('moodledocs'));
308
    }
309
 
310
}
311