Merge branch 'MDL-58898-master' of https://github.com/xow/moodle
[moodle.git] / auth / oauth2 / classes / api.php
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/>.
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;
26 use context_user;
27 use stdClass;
28 use moodle_exception;
29 use moodle_url;
31 defined('MOODLE_INTERNAL') || die();
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 {
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     }
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;
63         if ($userid === false) {
64             $userid = $USER->id;
65         }
67         if (\core\session\manager::is_loggedinas()) {
68             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
69         }
71         $context = context_user::instance($userid);
72         require_capability('auth/oauth2:managelinkedlogins', $context);
74         return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']);
75     }
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);
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     }
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;
114         if ($userid === false) {
115             $userid = $USER->id;
116         }
118         if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
119             throw new moodle_exception('alreadylinked', 'auth_oauth2');
120         }
122         if (\core\session\manager::is_loggedinas()) {
123             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
124         }
126         $context = context_user::instance($userid);
127         if (!$skippermissions) {
128             require_capability('auth/oauth2:managelinkedlogins', $context);
129         }
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     }
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();
170         $linkedlogin = new linked_login(0, $record);
171         $linkedlogin->create();
173         // Construct the email.
174         $site = get_site();
175         $supportuser = \core_user::get_support_user();
176         $user = get_complete_user_data('id', $userid);
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'));
185         $subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname));
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);
195         // Remove data parameter just in case it was included in the confirmation so we can add it manually later.
196         $data->link = $confirmationurl->out();
198         $message     = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
199         $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
201         $user->mailformat = 1;  // Always send HTML version as well.
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     }
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         ];
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     }
242     /**
243      * Send an email with a link to confirm creating this account.
244      *
245      * @param array $userinfo as returned from an oauth client.
246      * @param \core\oauth2\issuer $issuer
247      * @param int $userid (defaults to $USER->id)
248      * @return bool
249      */
250     public static function send_confirm_account_email($userinfo, $issuer) {
251         global $CFG, $DB;
252         require_once($CFG->dirroot.'/user/profile/lib.php');
253         require_once($CFG->dirroot.'/user/lib.php');
255         if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
256             throw new moodle_exception('alreadylinked', 'auth_oauth2');
257         }
259         $user = new stdClass();
260         $user->username = $userinfo['username'];
261         $user->email = $userinfo['email'];
262         $user->auth = 'oauth2';
263         $user->mnethostid = $CFG->mnet_localhost_id;
264         $user->lastname = isset($userinfo['lastname']) ? $userinfo['lastname'] : '';
265         $user->firstname = isset($userinfo['firstname']) ? $userinfo['firstname'] : '';
266         $user->url = isset($userinfo['url']) ? $userinfo['url'] : '';
267         $user->alternatename = isset($userinfo['alternatename']) ? $userinfo['alternatename'] : '';
268         $user->secret = random_string(15);
270         $user->password = '';
271         // This user is not confirmed.
272         $user->confirmed = 0;
274         $user->id = user_create_user($user, false, true);
276         // The linked account is pre-confirmed.
277         $record = new stdClass();
278         $record->issuerid = $issuer->get('id');
279         $record->username = $userinfo['username'];
280         $record->userid = $user->id;
281         $record->email = $userinfo['email'];
282         $record->confirmtoken = '';
283         $record->confirmtokenexpires = 0;
285         $linkedlogin = new linked_login(0, $record);
286         $linkedlogin->create();
288         // Construct the email.
289         $site = get_site();
290         $supportuser = \core_user::get_support_user();
291         $user = get_complete_user_data('id', $user->id);
293         $data = new stdClass();
294         $data->fullname = fullname($user);
295         $data->sitename  = format_string($site->fullname);
296         $data->admin     = generate_email_signoff();
298         $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
300         $params = [
301             'token' => $user->secret,
302             'username' => $userinfo['username']
303         ];
304         $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
306         $data->link = $confirmationurl->out();
308         $message     = get_string('confirmaccountemail', 'auth_oauth2', $data);
309         $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
311         $user->mailformat = 1;  // Always send HTML version as well.
313         // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
314         email_to_user($user, $supportuser, $subject, $message, $messagehtml);
315         return $user;
316     }
318     /**
319      * Delete linked login
320      *
321      * Requires auth/oauth2:managelinkedlogins capability at the user context.
322      *
323      * @param int $linkedloginid
324      * @return boolean
325      */
326     public static function delete_linked_login($linkedloginid) {
327         $login = new linked_login($linkedloginid);
328         $userid = $login->get('userid');
330         if (\core\session\manager::is_loggedinas()) {
331             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
332         }
334         $context = context_user::instance($userid);
335         require_capability('auth/oauth2:managelinkedlogins', $context);
337         $login->delete();
338     }
340     /**
341      * Delete linked logins for a user.
342      *
343      * @param \core\event\user_deleted $event
344      * @return boolean
345      */
346     public static function user_deleted(\core\event\user_deleted $event) {
347         global $DB;
349         $userid = $event->objectid;
351         return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
352     }