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
 
1441 ariadna 19
use core\clock;
20
use core\di;
21
use stdClass;
1 efrain 22
 
23
/**
24
 * Session handler base.
25
 *
26
 * @package    core
27
 * @copyright  2013 Petr Skoda {@link http://skodak.org}
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
abstract class handler {
31
    /** @var boolean $requireswritelock does the session need and/or have a lock? */
32
    protected $requireswritelock = false;
33
 
34
    /**
35
     * Start the session.
36
     * @return bool success
37
     */
38
    public function start() {
39
        return session_start();
40
    }
41
 
42
    /**
43
     * Write the session and release lock. If the session was not intentionally opened
44
     * with a write lock, then we will abort the session instead if able.
45
     */
46
    public function write_close() {
47
        if ($this->requires_write_lock()) {
48
            session_write_close();
49
            $this->requireswritelock = false;
50
        } else {
51
            $this->abort();
52
        }
53
    }
54
 
55
    /**
56
     * Release lock on the session without writing it.
57
     * May not be possible in older versions of PHP. If so, session may be written anyway
58
     * so that any locks are released.
59
     */
60
    public function abort() {
61
        session_abort();
62
        $this->requireswritelock = false;
63
    }
64
 
65
    /**
66
     * This is called after init() and before start() to indicate whether the session
67
     * opened should be writable or not. This is intentionally captured even if your
68
     * handler doesn't support non-locking sessions, so that behavior (upon session close)
69
     * matches closely between handlers.
70
     * @param bool $requireswritelock true if needs to be open for writing
71
     */
72
    public function set_requires_write_lock($requireswritelock) {
73
        $this->requireswritelock = $requireswritelock;
74
    }
75
 
76
    /**
1441 ariadna 77
     * Returns all session records.
78
     *
79
     * @return \Iterator
80
     */
81
    public function get_all_sessions(): \Iterator {
82
        global $DB;
83
 
84
        $rs = $DB->get_recordset('sessions');
85
        foreach ($rs as $row) {
86
            yield $row;
87
        }
88
        $rs->close();
89
    }
90
 
91
    /**
92
     * Returns a single session record for this session id.
93
     *
94
     * @param string $sid
95
     * @return stdClass
96
     */
97
    public function get_session_by_sid(string $sid): stdClass {
98
        global $DB;
99
 
100
        return $DB->get_record('sessions', ['sid' => $sid]) ?: new stdClass();
101
    }
102
 
103
    /**
104
     * Returns all the session records for this user id.
105
     *
106
     * @param int $userid
107
     * @return array
108
     */
109
    public function get_sessions_by_userid(int $userid): array {
110
        global $DB;
111
 
112
        return $DB->get_records('sessions', ['userid' => $userid]);
113
    }
114
 
115
    /**
116
     * Insert new empty session record.
117
     *
118
     * @param int $userid
119
     * @return stdClass the new record
120
     */
121
    public function add_session(int $userid): stdClass {
122
        global $DB;
123
 
124
        $record = new stdClass();
125
        $record->state       = 0;
126
        $record->sid         = session_id();
127
        $record->sessdata    = null;
128
        $record->userid      = $userid;
129
        $record->timecreated = $record->timemodified = di::get(clock::class)->time();
130
        $record->firstip     = $record->lastip = getremoteaddr();
131
 
132
        $record->id = $DB->insert_record('sessions', $record);
133
 
134
        return $record;
135
    }
136
 
137
    /**
138
     * Update a session record.
139
     *
140
     * @param stdClass $record
141
     * @return bool
142
     */
143
    public function update_session(stdClass $record): bool {
144
        global $DB;
145
 
146
        if (!isset($record->id) && isset($record->sid)) {
147
            $record->id = $DB->get_field('sessions', 'id', ['sid' => $record->sid]);
148
        }
149
 
150
        return $DB->update_record('sessions', $record);
151
    }
152
 
153
    /**
154
     * Destroy a specific session and delete this session record for this session id.
155
     *
156
     * @param string $id session id
157
     * @return bool
158
     */
159
    public function destroy(string $id): bool {
160
        global $DB;
161
 
162
        return $DB->delete_records('sessions', ['sid' => $id]);
163
    }
164
 
165
    /**
166
     * Destroy all sessions, and delete all the session data.
167
     *
168
     * @return bool
169
     */
170
    public function destroy_all(): bool {
171
        global $DB;
172
 
173
        return $DB->delete_records('sessions');
174
    }
175
 
176
    /**
177
     * Clean up expired sessions.
178
     *
179
     * @param int $purgebefore Sessions that have not updated for the last purgebefore timestamp will be removed.
180
     * @param int $userid
181
     */
182
    protected function destroy_expired_user_sessions(int $purgebefore, int $userid): void {
183
        $sessions = $this->get_sessions_by_userid($userid);
184
        foreach ($sessions as $session) {
185
            if ($session->timemodified < $purgebefore) {
186
                $this->destroy($session->sid);
187
            }
188
        }
189
    }
190
 
191
    /**
192
     * Clean up all expired sessions.
193
     *
194
     * @param int $purgebefore
195
     */
196
    protected function destroy_all_expired_sessions(int $purgebefore): void {
197
        global $DB, $CFG;
198
 
199
        $authsequence = get_enabled_auth_plugins();
200
        $authsequence = array_flip($authsequence);
201
        unset($authsequence['nologin']); // No login means user cannot login.
202
        $authsequence = array_flip($authsequence);
203
        $authplugins = [];
204
        foreach ($authsequence as $authname) {
205
            $authplugins[$authname] = get_auth_plugin($authname);
206
        }
207
        $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
208
                  FROM {user} u
209
                  JOIN {sessions} s ON s.userid = u.id
210
                 WHERE s.timemodified < :purgebefore AND u.id <> :guestid";
211
        $params = ['purgebefore' => $purgebefore, 'guestid' => $CFG->siteguest];
212
 
213
        $rs = $DB->get_recordset_sql($sql, $params);
214
        foreach ($rs as $user) {
215
            foreach ($authplugins as $authplugin) {
216
                if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
217
                    continue 2;
218
                }
219
            }
220
            $this->destroy($user->sid);
221
        }
222
        $rs->close();
223
    }
224
 
225
    /**
226
     * Destroy all sessions for a given plugin.
227
     * Typically used when a plugin is disabled or uninstalled, so all sessions (users) for that plugin are logged out.
228
     *
229
     * @param string $pluginname Auth plugin name.
230
     */
231
    public function destroy_by_auth_plugin(string $pluginname): void {
232
        global $DB;
233
 
234
        $rs = $DB->get_recordset('user', ['auth' => $pluginname], 'id ASC', 'id');
235
        foreach ($rs as $user) {
236
            $sessions = $this->get_sessions_by_userid($user->id);
237
            foreach ($sessions as $session) {
238
                $this->destroy($session->sid);
239
            }
240
        }
241
        $rs->close();
242
    }
243
 
244
    // phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameUnderscore
245
    /**
246
     * Periodic timed-out session cleanup.
247
     *
248
     * @param int $max_lifetime Sessions that have not updated for the last max_lifetime seconds will be removed.
249
     * @return int|false Number of deleted sessions or false if an error occurred.
250
     */
251
    public function gc(int $max_lifetime = 0): int|false {
252
        global $CFG;
253
 
254
        // This may take a long time.
255
        \core_php_time_limit::raise();
256
 
257
        if ($max_lifetime === 0) {
258
            $max_lifetime = (int) $CFG->sessiontimeout;
259
        }
260
 
261
        try {
262
            // Calculate the timestamp before which sessions are considered expired.
263
            $purgebefore = di::get(clock::class)->time() - $max_lifetime;
264
 
265
            // Delete expired sessions for guest user account.
266
            $this->destroy_expired_user_sessions($purgebefore, $CFG->siteguest);
267
 
268
            // Delete expired sessions for userid = 0 (not logged in), better kill them asap to release memory.
269
            $this->destroy_expired_user_sessions($purgebefore, 0);
270
 
271
            // Clean up expired sessions for real users only.
272
            $this->destroy_all_expired_sessions($purgebefore);
273
 
274
            // Cleanup leftovers from the first browser access because it may set multiple cookies and then use only one.
275
            $purgebefore = di::get(clock::class)->time() - (60 * 3);
276
            $sessions = $this->get_sessions_by_userid(0);
277
            foreach ($sessions as $session) {
278
                if ($session->timemodified == $session->timecreated && $session->timemodified < $purgebefore) {
279
                    $this->destroy($session->sid);
280
                }
281
            }
282
 
283
        } catch (\Exception $ex) {
284
            debugging('Error gc-ing sessions: '.$ex->getMessage(), DEBUG_NORMAL, $ex->getTrace());
285
        }
286
 
287
        return 0;
288
    }
289
    // phpcs:enable
290
 
291
    /**
1 efrain 292
     * Has this session been opened with a writelock? Your handler should call this during
293
     * start() if you support read-only sessions.
294
     * @return bool true if session is intended to have a write lock.
295
     */
296
    public function requires_write_lock() {
297
        return $this->requireswritelock;
298
    }
299
 
300
    /**
301
     * Init session handler.
302
     */
303
    abstract public function init();
304
 
305
    /**
306
     * Check the backend contains data for this session id.
307
     *
308
     * Note: this is intended to be called from manager::session_exists() only.
309
     *
310
     * @param string $sid
311
     * @return bool true if session found.
312
     */
313
    abstract public function session_exists($sid);
314
}