Proyectos de Subversion Moodle

Rev

| 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)
58
     * @return boolean
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
82
     * @return stdClass User record if found.
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.
109
     * @return bool
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();
179
        $data->fullname = fullname($user);
180
        $data->sitename  = format_string($site->fullname);
181
        $data->admin     = generate_email_signoff();
182
        $data->issuername = format_string($issuer->get('name'));
183
        $data->linkedemail = format_string($linkedlogin->get('email'));
184
 
185
        $subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname));
186
 
187
        $params = [
188
            'token' => $linkedlogin->get('confirmtoken'),
189
            'userid' => $userid,
190
            'username' => $userinfo['username'],
191
            'issuerid' => $issuer->get('id'),
192
        ];
193
        $confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params);
194
 
195
        $data->link = $confirmationurl->out(false);
196
        $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
197
 
198
        $data->link = $confirmationurl->out();
199
        $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
200
 
201
        $user->mailformat = 1;  // Always send HTML version as well.
202
 
203
        // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
204
        return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
205
    }
206
 
207
    /**
208
     * Look for a waiting confirmation token, and if we find a match - confirm it.
209
     *
210
     * @param int $userid
211
     * @param string $username
212
     * @param int $issuerid
213
     * @param string $token
214
     * @return boolean True if we linked.
215
     */
216
    public static function confirm_link_login($userid, $username, $issuerid, $token) {
217
        if (empty($token) || empty($userid) || empty($issuerid) || empty($username)) {
218
            return false;
219
        }
220
        $params = [
221
            'userid' => $userid,
222
            'username' => $username,
223
            'issuerid' => $issuerid,
224
            'confirmtoken' => $token,
225
        ];
226
 
227
        $login = linked_login::get_record($params);
228
        if (empty($login)) {
229
            return false;
230
        }
231
        $expires = $login->get('confirmtokenexpires');
232
        if (time() > $expires) {
233
            $login->delete();
234
            return;
235
        }
236
        $login->set('confirmtokenexpires', 0);
237
        $login->set('confirmtoken', '');
238
        $login->update();
239
        return true;
240
    }
241
 
242
    /**
243
     * Create an account with a linked login that is already confirmed.
244
     *
245
     * @param array $userinfo as returned from an oauth client.
246
     * @param \core\oauth2\issuer $issuer
247
     * @return bool
248
     */
249
    public static function create_new_confirmed_account($userinfo, $issuer) {
250
        global $CFG, $DB;
251
        require_once($CFG->dirroot.'/user/profile/lib.php');
252
        require_once($CFG->dirroot.'/user/lib.php');
253
 
254
        $user = new stdClass();
255
        $user->auth = 'oauth2';
256
        $user->mnethostid = $CFG->mnet_localhost_id;
257
        $user->secret = random_string(15);
258
        $user->password = '';
259
        $user->confirmed = 1;  // Set the user to confirmed.
260
 
261
        $user = self::save_user($userinfo, $user);
262
 
263
        // The linked account is pre-confirmed.
264
        $record = new stdClass();
265
        $record->issuerid = $issuer->get('id');
266
        $record->username = $userinfo['username'];
267
        $record->userid = $user->id;
268
        $record->email = $userinfo['email'];
269
        $record->confirmtoken = '';
270
        $record->confirmtokenexpires = 0;
271
 
272
        $linkedlogin = new linked_login(0, $record);
273
        $linkedlogin->create();
274
 
275
        return $user;
276
    }
277
 
278
    /**
279
     * Send an email with a link to confirm creating this account.
280
     *
281
     * @param array $userinfo as returned from an oauth client.
282
     * @param \core\oauth2\issuer $issuer
283
     * @param int $userid (defaults to $USER->id)
284
     * @return bool
285
     */
286
    public static function send_confirm_account_email($userinfo, $issuer) {
287
        global $CFG, $DB;
288
        require_once($CFG->dirroot.'/user/profile/lib.php');
289
        require_once($CFG->dirroot.'/user/lib.php');
290
 
291
        if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
292
            throw new moodle_exception('alreadylinked', 'auth_oauth2');
293
        }
294
 
295
        $user = new stdClass();
296
        $user->auth = 'oauth2';
297
        $user->mnethostid = $CFG->mnet_localhost_id;
298
        $user->secret = random_string(15);
299
        $user->password = '';
300
        $user->confirmed = 0;  // The user is not yet confirmed.
301
 
302
        $user = self::save_user($userinfo, $user);
303
 
304
        // The linked account is pre-confirmed.
305
        $record = new stdClass();
306
        $record->issuerid = $issuer->get('id');
307
        $record->username = $userinfo['username'];
308
        $record->userid = $user->id;
309
        $record->email = $userinfo['email'];
310
        $record->confirmtoken = '';
311
        $record->confirmtokenexpires = 0;
312
 
313
        $linkedlogin = new linked_login(0, $record);
314
        $linkedlogin->create();
315
 
316
        // Construct the email.
317
        $site = get_site();
318
        $supportuser = \core_user::get_support_user();
319
        $user = get_complete_user_data('id', $user->id);
320
 
321
        $data = new stdClass();
322
        $data->fullname = fullname($user);
323
        $data->sitename  = format_string($site->fullname);
324
        $data->admin     = generate_email_signoff();
325
 
326
        $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
327
 
328
        $params = [
329
            'token' => $user->secret,
330
            'username' => $userinfo['username']
331
        ];
332
        $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
333
 
334
        $data->link = $confirmationurl->out(false);
335
        $message = get_string('confirmaccountemail', 'auth_oauth2', $data);
336
 
337
        $data->link = $confirmationurl->out();
338
        $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
339
 
340
        $user->mailformat = 1;  // Always send HTML version as well.
341
 
342
        // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
343
        email_to_user($user, $supportuser, $subject, $message, $messagehtml);
344
        return $user;
345
    }
346
 
347
    /**
348
     * Delete linked login
349
     *
350
     * Requires auth/oauth2:managelinkedlogins capability at the user context.
351
     *
352
     * @param int $linkedloginid
353
     * @return boolean
354
     */
355
    public static function delete_linked_login($linkedloginid) {
356
        $login = new linked_login($linkedloginid);
357
        $userid = $login->get('userid');
358
 
359
        if (\core\session\manager::is_loggedinas()) {
360
            throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
361
        }
362
 
363
        $context = context_user::instance($userid);
364
        require_capability('auth/oauth2:managelinkedlogins', $context);
365
 
366
        $login->delete();
367
    }
368
 
369
    /**
370
     * Delete linked logins for a user.
371
     *
372
     * @param \core\event\user_deleted $event
373
     * @return boolean
374
     */
375
    public static function user_deleted(\core\event\user_deleted $event) {
376
        global $DB;
377
 
378
        $userid = $event->objectid;
379
 
380
        return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
381
    }
382
 
383
    /**
384
     * Is the plugin enabled.
385
     *
386
     * @return bool
387
     */
388
    public static function is_enabled() {
389
        return is_enabled_auth('oauth2');
390
    }
391
 
392
    /**
393
     * Create a new user & update the profile fields
394
     *
395
     * @param array $userinfo
396
     * @param object $user
397
     * @return object
398
     */
399
    private static function save_user(array $userinfo, object $user): object {
400
        // Map supplied issuer user info to Moodle user fields.
401
        $userfieldmapping = new \core\oauth2\user_field_mapping();
402
        $userfieldlist = $userfieldmapping->get_internalfields();
403
        $hasprofilefield = false;
404
        foreach ($userfieldlist as $field) {
405
            if (isset($userinfo[$field]) && $userinfo[$field]) {
406
                $user->$field = $userinfo[$field];
407
 
408
                // Check whether the profile fields exist or not.
409
                $hasprofilefield = $hasprofilefield || strpos($field, \core_user\fields::PROFILE_FIELD_PREFIX) === 0;
410
            }
411
        }
412
 
413
        // Create a new user.
414
        $user->id = user_create_user($user, false, true);
415
 
416
        // If profile fields exist then save custom profile fields data.
417
        if ($hasprofilefield) {
418
            profile_save_data($user);
419
        }
420
 
421
        return $user;
422
    }
423
}