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
 * Class for loading/storing oauth2 linked logins from the DB.
19
 *
20
 * @package    auth_oauth2
21
 * @copyright  2017 Damyon Wiese
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace auth_oauth2;
25
 
26
use context_user;
27
use stdClass;
28
use moodle_exception;
29
use moodle_url;
30
 
31
defined('MOODLE_INTERNAL') || die();
32
 
33
/**
34
 * Static list of api methods for auth oauth2 configuration.
35
 *
36
 * @package    auth_oauth2
37
 * @copyright  2017 Damyon Wiese
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class api {
41
 
42
    /**
43
     * Remove all linked logins that are using issuers that have been deleted.
44
     *
45
     * @param int $issuerid The issuer id of the issuer to check, or false to check all (defaults to all)
46
     * @return boolean
47
     */
48
    public static function clean_orphaned_linked_logins($issuerid = false) {
49
        return linked_login::delete_orphaned($issuerid);
50
    }
51
 
52
    /**
53
     * List linked logins
54
     *
55
     * Requires auth/oauth2:managelinkedlogins capability at the user context.
56
     *
57
     * @param int $userid (defaults to $USER->id)
1441 ariadna 58
     * @return linked_login[]
1 efrain 59
     */
60
    public static function get_linked_logins($userid = false) {
61
        global $USER;
62
 
63
        if ($userid === false) {
64
            $userid = $USER->id;
65
        }
66
 
67
        if (\core\session\manager::is_loggedinas()) {
68
            throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
69
        }
70
 
71
        $context = context_user::instance($userid);
72
        require_capability('auth/oauth2:managelinkedlogins', $context);
73
 
74
        return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']);
75
    }
76
 
77
    /**
78
     * See if there is a match for this username and issuer in the linked_login table.
79
     *
80
     * @param string $username as returned from an oauth client.
81
     * @param \core\oauth2\issuer $issuer
1441 ariadna 82
     * @return linked_login|false record if found and user exists, false otherwise.
1 efrain 83
     */
84
    public static function match_username_to_user($username, $issuer) {
85
        $params = [
86
            'issuerid' => $issuer->get('id'),
87
            'username' => $username
88
        ];
89
        $result = linked_login::get_record($params);
90
 
91
        if ($result) {
92
            $user = \core_user::get_user($result->get('userid'));
93
            if (!empty($user) && !$user->deleted) {
94
                return $result;
95
            }
96
        }
97
        return false;
98
    }
99
 
100
    /**
101
     * Link a login to this account.
102
     *
103
     * Requires auth/oauth2:managelinkedlogins capability at the user context.
104
     *
105
     * @param array $userinfo as returned from an oauth client.
106
     * @param \core\oauth2\issuer $issuer
107
     * @param int $userid (defaults to $USER->id)
108
     * @param bool $skippermissions During signup we need to set this before the user is setup for capability checks.
1441 ariadna 109
     * @return linked_login
1 efrain 110
     */
111
    public static function link_login($userinfo, $issuer, $userid = false, $skippermissions = false) {
112
        global $USER;
113
 
114
        if ($userid === false) {
115
            $userid = $USER->id;
116
        }
117
 
118
        if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
119
            throw new moodle_exception('alreadylinked', 'auth_oauth2');
120
        }
121
 
122
        if (\core\session\manager::is_loggedinas()) {
123
            throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
124
        }
125
 
126
        $context = context_user::instance($userid);
127
        if (!$skippermissions) {
128
            require_capability('auth/oauth2:managelinkedlogins', $context);
129
        }
130
 
131
        $record = new stdClass();
132
        $record->issuerid = $issuer->get('id');
133
        $record->username = $userinfo['username'];
134
        $record->userid = $userid;
135
        $existing = linked_login::get_record((array)$record);
136
        if ($existing) {
137
            $existing->set('confirmtoken', '');
138
            $existing->update();
139
            return $existing;
140
        }
141
        $record->email = $userinfo['email'];
142
        $record->confirmtoken = '';
143
        $record->confirmtokenexpires = 0;
144
        $linkedlogin = new linked_login(0, $record);
145
        return $linkedlogin->create();
146
    }
147
 
148
    /**
149
     * Send an email with a link to confirm linking this account.
150
     *
151
     * @param array $userinfo as returned from an oauth client.
152
     * @param \core\oauth2\issuer $issuer
153
     * @param int $userid (defaults to $USER->id)
154
     * @return bool
155
     */
156
    public static function send_confirm_link_login_email($userinfo, $issuer, $userid) {
157
        $record = new stdClass();
158
        $record->issuerid = $issuer->get('id');
159
        $record->username = $userinfo['username'];
160
        $record->userid = $userid;
161
        if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
162
            throw new moodle_exception('alreadylinked', 'auth_oauth2');
163
        }
164
        $record->email = $userinfo['email'];
165
        $record->confirmtoken = random_string(32);
166
        $expires = new \DateTime('NOW');
167
        $expires->add(new \DateInterval('PT30M'));
168
        $record->confirmtokenexpires = $expires->getTimestamp();
169
 
170
        $linkedlogin = new linked_login(0, $record);
171
        $linkedlogin->create();
172
 
173
        // Construct the email.
174
        $site = get_site();
175
        $supportuser = \core_user::get_support_user();
176
        $user = get_complete_user_data('id', $userid);
177
 
178
        $data = new stdClass();
1441 ariadna 179
        $placeholders = \core_user::get_name_placeholders($user);
180
        foreach ($placeholders as $field => $value) {
181
            $data->{$field} = $value;
182
        }
1 efrain 183
        $data->sitename  = format_string($site->fullname);
184
        $data->admin     = generate_email_signoff();
185
        $data->issuername = format_string($issuer->get('name'));
186
        $data->linkedemail = format_string($linkedlogin->get('email'));
187
 
188
        $subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname));
189
 
190
        $params = [
191
            'token' => $linkedlogin->get('confirmtoken'),
192
            'userid' => $userid,
193
            'username' => $userinfo['username'],
194
            'issuerid' => $issuer->get('id'),
195
        ];
196
        $confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params);
197
 
198
        $data->link = $confirmationurl->out(false);
199
        $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
200
 
201
        $data->link = $confirmationurl->out();
202
        $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
203
 
204
        $user->mailformat = 1;  // Always send HTML version as well.
205
 
206
        // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
207
        return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
208
    }
209
 
210
    /**
211
     * Look for a waiting confirmation token, and if we find a match - confirm it.
212
     *
213
     * @param int $userid
214
     * @param string $username
215
     * @param int $issuerid
216
     * @param string $token
217
     * @return boolean True if we linked.
218
     */
219
    public static function confirm_link_login($userid, $username, $issuerid, $token) {
220
        if (empty($token) || empty($userid) || empty($issuerid) || empty($username)) {
221
            return false;
222
        }
223
        $params = [
224
            'userid' => $userid,
225
            'username' => $username,
226
            'issuerid' => $issuerid,
227
            'confirmtoken' => $token,
228
        ];
229
 
230
        $login = linked_login::get_record($params);
231
        if (empty($login)) {
232
            return false;
233
        }
234
        $expires = $login->get('confirmtokenexpires');
235
        if (time() > $expires) {
236
            $login->delete();
1441 ariadna 237
            return false;
1 efrain 238
        }
239
        $login->set('confirmtokenexpires', 0);
240
        $login->set('confirmtoken', '');
241
        $login->update();
242
        return true;
243
    }
244
 
245
    /**
246
     * Create an account with a linked login that is already confirmed.
247
     *
248
     * @param array $userinfo as returned from an oauth client.
249
     * @param \core\oauth2\issuer $issuer
1441 ariadna 250
     * @return stdClass
1 efrain 251
     */
252
    public static function create_new_confirmed_account($userinfo, $issuer) {
253
        global $CFG, $DB;
254
        require_once($CFG->dirroot.'/user/profile/lib.php');
255
        require_once($CFG->dirroot.'/user/lib.php');
256
 
257
        $user = new stdClass();
258
        $user->auth = 'oauth2';
259
        $user->mnethostid = $CFG->mnet_localhost_id;
260
        $user->secret = random_string(15);
261
        $user->password = '';
262
        $user->confirmed = 1;  // Set the user to confirmed.
263
 
264
        $user = self::save_user($userinfo, $user);
265
 
266
        // The linked account is pre-confirmed.
267
        $record = new stdClass();
268
        $record->issuerid = $issuer->get('id');
269
        $record->username = $userinfo['username'];
270
        $record->userid = $user->id;
271
        $record->email = $userinfo['email'];
272
        $record->confirmtoken = '';
273
        $record->confirmtokenexpires = 0;
274
 
275
        $linkedlogin = new linked_login(0, $record);
276
        $linkedlogin->create();
277
 
278
        return $user;
279
    }
280
 
281
    /**
282
     * Send an email with a link to confirm creating this account.
283
     *
284
     * @param array $userinfo as returned from an oauth client.
285
     * @param \core\oauth2\issuer $issuer
286
     * @param int $userid (defaults to $USER->id)
1441 ariadna 287
     * @return stdClass
1 efrain 288
     */
289
    public static function send_confirm_account_email($userinfo, $issuer) {
290
        global $CFG, $DB;
291
        require_once($CFG->dirroot.'/user/profile/lib.php');
292
        require_once($CFG->dirroot.'/user/lib.php');
293
 
294
        if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
295
            throw new moodle_exception('alreadylinked', 'auth_oauth2');
296
        }
297
 
298
        $user = new stdClass();
299
        $user->auth = 'oauth2';
300
        $user->mnethostid = $CFG->mnet_localhost_id;
301
        $user->secret = random_string(15);
302
        $user->password = '';
303
        $user->confirmed = 0;  // The user is not yet confirmed.
304
 
305
        $user = self::save_user($userinfo, $user);
306
 
307
        // The linked account is pre-confirmed.
308
        $record = new stdClass();
309
        $record->issuerid = $issuer->get('id');
310
        $record->username = $userinfo['username'];
311
        $record->userid = $user->id;
312
        $record->email = $userinfo['email'];
313
        $record->confirmtoken = '';
314
        $record->confirmtokenexpires = 0;
315
 
316
        $linkedlogin = new linked_login(0, $record);
317
        $linkedlogin->create();
318
 
319
        // Construct the email.
320
        $site = get_site();
321
        $supportuser = \core_user::get_support_user();
322
        $user = get_complete_user_data('id', $user->id);
323
 
324
        $data = new stdClass();
1441 ariadna 325
        $placeholders = \core_user::get_name_placeholders($user);
326
        foreach ($placeholders as $field => $value) {
327
            $data->{$field} = $value;
328
        }
1 efrain 329
        $data->sitename  = format_string($site->fullname);
330
        $data->admin     = generate_email_signoff();
331
 
332
        $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
333
 
334
        $params = [
335
            'token' => $user->secret,
336
            'username' => $userinfo['username']
337
        ];
338
        $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
339
 
340
        $data->link = $confirmationurl->out(false);
341
        $message = get_string('confirmaccountemail', 'auth_oauth2', $data);
342
 
343
        $data->link = $confirmationurl->out();
344
        $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
345
 
346
        $user->mailformat = 1;  // Always send HTML version as well.
347
 
348
        // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
349
        email_to_user($user, $supportuser, $subject, $message, $messagehtml);
350
        return $user;
351
    }
352
 
353
    /**
1441 ariadna 354
     * Delete a users own linked login
1 efrain 355
     *
356
     * Requires auth/oauth2:managelinkedlogins capability at the user context.
357
     *
358
     * @param int $linkedloginid
359
     */
360
    public static function delete_linked_login($linkedloginid) {
1441 ariadna 361
        global $USER;
1 efrain 362
 
363
        if (\core\session\manager::is_loggedinas()) {
364
            throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
365
        }
366
 
1441 ariadna 367
        $login = linked_login::get_record([
368
            'id' => $linkedloginid,
369
            'userid' => $USER->id,
370
            'confirmtoken' => '',
371
        ], MUST_EXIST);
372
 
373
        $context = context_user::instance($login->get('userid'));
1 efrain 374
        require_capability('auth/oauth2:managelinkedlogins', $context);
375
 
376
        $login->delete();
377
    }
378
 
379
    /**
380
     * Delete linked logins for a user.
381
     *
382
     * @param \core\event\user_deleted $event
383
     * @return boolean
384
     */
385
    public static function user_deleted(\core\event\user_deleted $event) {
386
        global $DB;
387
 
388
        $userid = $event->objectid;
389
 
390
        return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
391
    }
392
 
393
    /**
394
     * Is the plugin enabled.
395
     *
396
     * @return bool
397
     */
398
    public static function is_enabled() {
399
        return is_enabled_auth('oauth2');
400
    }
401
 
402
    /**
403
     * Create a new user & update the profile fields
404
     *
405
     * @param array $userinfo
406
     * @param object $user
1441 ariadna 407
     * @return stdClass
1 efrain 408
     */
409
    private static function save_user(array $userinfo, object $user): object {
410
        // Map supplied issuer user info to Moodle user fields.
411
        $userfieldmapping = new \core\oauth2\user_field_mapping();
412
        $userfieldlist = $userfieldmapping->get_internalfields();
413
        $hasprofilefield = false;
414
        foreach ($userfieldlist as $field) {
415
            if (isset($userinfo[$field]) && $userinfo[$field]) {
416
                $user->$field = $userinfo[$field];
417
 
418
                // Check whether the profile fields exist or not.
419
                $hasprofilefield = $hasprofilefield || strpos($field, \core_user\fields::PROFILE_FIELD_PREFIX) === 0;
420
            }
421
        }
422
 
423
        // Create a new user.
424
        $user->id = user_create_user($user, false, true);
425
 
426
        // If profile fields exist then save custom profile fields data.
427
        if ($hasprofilefield) {
428
            profile_save_data($user);
429
        }
430
 
431
        return $user;
432
    }
433
}