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
namespace core\session;
18
 
19
use SessionHandlerInterface;
20
 
21
/**
22
 * Database based session handler.
23
 *
24
 * @package    core
25
 * @copyright  2013 Petr Skoda {@link http://skodak.org}
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
class database extends handler implements SessionHandlerInterface {
1441 ariadna 29
 
30
    /** @var int $record session record */
1 efrain 31
    protected $recordid = null;
32
 
33
    /** @var \moodle_database $database session database */
34
    protected $database = null;
35
 
36
    /** @var bool $failed session read/init failed, do not write back to DB */
37
    protected $failed = false;
38
 
39
    /** @var string $lasthash hash of the session data content */
40
    protected $lasthash = null;
41
 
42
    /** @var int $acquiretimeout how long to wait for session lock */
43
    protected $acquiretimeout = 120;
44
 
45
    /**
46
     * Create new instance of handler.
47
     */
48
    public function __construct() {
49
        global $DB, $CFG;
50
        // Note: we store the reference here because we need to modify database in shutdown handler.
51
        $this->database = $DB;
52
 
53
        if (!empty($CFG->session_database_acquire_lock_timeout)) {
54
            $this->acquiretimeout = (int)$CFG->session_database_acquire_lock_timeout;
55
        }
56
    }
57
 
1441 ariadna 58
    #[\Override]
1 efrain 59
    public function init() {
60
        if (!$this->database->session_lock_supported()) {
61
            throw new exception('sessionhandlerproblem', 'error', '', null, 'Database does not support session locking');
62
        }
63
 
64
        $result = session_set_save_handler($this);
65
        if (!$result) {
66
            throw new exception('dbsessionhandlerproblem', 'error');
67
        }
68
    }
69
 
1441 ariadna 70
    #[\Override]
1 efrain 71
    public function session_exists($sid) {
72
        // It was already checked in the calling code that the record in sessions table exists.
73
        return true;
74
    }
75
 
1441 ariadna 76
    #[\Override]
77
    public function destroy(string $id): bool {
78
        if (!$session = $this->database->get_record('sessions', ['sid' => $id], 'id, sid')) {
79
            if ($id == session_id()) {
80
                $this->recordid = null;
81
                $this->lasthash = null;
82
            }
83
            return true;
84
        }
1 efrain 85
 
1441 ariadna 86
        if ($this->recordid && ($session->id == $this->recordid)) {
87
            try {
88
                $this->database->release_session_lock($this->recordid);
89
            } catch (\Exception $ex) {
90
                // Log and ignore any problems.
91
                mtrace('Failed to release session lock: '.$ex->getMessage());
92
            }
93
            $this->recordid = null;
94
            $this->lasthash = null;
95
        }
96
 
97
        $this->database->delete_records('sessions', ['id' => $session->id]);
98
 
99
        return true;
1 efrain 100
    }
101
 
102
    /**
103
     * Open session handler.
104
     *
105
     * {@see http://php.net/manual/en/function.session-set-save-handler.php}
106
     *
107
     * @param string $path
108
     * @param string $name
109
     * @return bool success
110
     */
111
    public function open(string $path, string $name): bool {
112
        // Note: we use the already open database.
113
        return true;
114
    }
115
 
116
    /**
117
     * Close session handler.
118
     *
119
     * {@see http://php.net/manual/en/function.session-set-save-handler.php}
120
     *
121
     * @return bool success
122
     */
123
    public function close(): bool {
124
        if ($this->recordid) {
125
            try {
126
                $this->database->release_session_lock($this->recordid);
127
            } catch (\Exception $ex) {
128
                // Ignore any problems.
129
            }
130
        }
131
        $this->recordid = null;
132
        $this->lasthash = null;
133
        return true;
134
    }
135
 
136
    /**
137
     * Read session handler.
138
     *
139
     * {@see http://php.net/manual/en/function.session-set-save-handler.php}
140
     *
141
     * @param string $sid
142
     * @return string|false
143
     */
144
    public function read(string $sid): string|false {
145
        try {
1441 ariadna 146
            if (!$record = $this->database->get_record('sessions', ['sid' => $sid])) {
1 efrain 147
                // Let's cheat and skip locking if this is the first access,
148
                // do not create the record here, let the manager do it after session init.
149
                $this->failed = false;
150
                $this->recordid = null;
151
                $this->lasthash = sha1('');
152
                return '';
153
            }
154
            if ($this->recordid and $this->recordid != $record->id) {
155
                error_log('Second session read with different record id detected, cannot read session');
156
                $this->failed = true;
157
                $this->recordid = null;
158
                return '';
159
            }
160
            if (!$this->recordid) {
161
                // Lock session if exists and not already locked.
162
                if ($this->requires_write_lock()) {
163
                    $this->database->get_session_lock($record->id, $this->acquiretimeout);
164
                }
165
                $this->recordid = $record->id;
166
            }
167
        } catch (\dml_sessionwait_exception $ex) {
168
            // This is a fatal error, better inform users.
169
            // It should not happen very often - all pages that need long time to execute
170
            // should close session immediately after access control checks.
171
            error_log('Cannot obtain session lock for sid: '.$sid);
172
            $this->failed = true;
173
            throw $ex;
174
 
175
        } catch (\Exception $ex) {
176
            // Do not rethrow exceptions here, this should not happen.
177
            error_log('Unknown exception when starting database session : '.$sid.' - '.$ex->getMessage());
178
            $this->failed = true;
179
            $this->recordid = null;
180
            return '';
181
        }
182
 
183
        // Finally read the full session data because we know we have the lock now.
184
        if (!$record = $this->database->get_record('sessions', array('id'=>$record->id), 'id, sessdata')) {
185
            // Ignore - something else just deleted the session record.
186
            $this->failed = true;
187
            $this->recordid = null;
188
            return '';
189
        }
190
        $this->failed = false;
191
 
192
        if (is_null($record->sessdata)) {
193
            $data = '';
194
            $this->lasthash = sha1('');
195
        } else {
196
            $data = base64_decode($record->sessdata);
197
            $this->lasthash = sha1($record->sessdata);
198
        }
199
 
200
        return $data;
201
    }
202
 
203
    /**
204
     * Write session handler.
205
     *
206
     * {@see http://php.net/manual/en/function.session-set-save-handler.php}
207
     *
208
     * NOTE: Do not write to output or throw any exceptions!
209
     *       Hopefully the next page is going to display nice error or it recovers...
210
     *
211
     * @param string $id
212
     * @param string $data
213
     * @return bool success
214
     */
215
    public function write(string $id, string $data): bool {
216
        if ($this->failed) {
217
            // Do not write anything back - we failed to start the session properly.
218
            return false;
219
        }
220
 
221
        // There might be some binary mess.
222
        $sessdata = base64_encode($data);
223
        $hash = sha1($sessdata);
224
 
225
        if ($hash === $this->lasthash) {
226
            return true;
227
        }
228
 
229
        try {
230
            if ($this->recordid) {
231
                $this->database->set_field('sessions', 'sessdata', $sessdata, ['id' => $this->recordid]);
232
            } else {
233
                // This happens in the first request when session record was just created in manager.
234
                $this->database->set_field('sessions', 'sessdata', $sessdata, ['sid' => $id]);
235
            }
236
        } catch (\Exception $ex) {
237
            // Do not rethrow exceptions here, this should not happen.
238
            // phpcs:ignore moodle.PHP.ForbiddenFunctions.FoundWithAlternative
239
            error_log(
240
                "Unknown exception when writing database session data : {$id} - " . $ex->getMessage(),
241
            );
242
        }
243
 
244
        return true;
245
    }
246
 
247
}