Merge branch 'MDL-58898-master' of https://github.com/xow/moodle
[moodle.git] / auth / oauth2 / classes / api.php
CommitLineData
1dca8d1a
DW
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 */
24namespace auth_oauth2;
25
26use context_user;
27use stdClass;
28use moodle_exception;
28b592d5 29use moodle_url;
1dca8d1a
DW
30
31defined('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 */
40class api {
41
4c8727ba
JO
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
1dca8d1a
DW
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
28b592d5 74 return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']);
1dca8d1a
DW
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 ];
512e681a
DW
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;
1dca8d1a
DW
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)
28b592d5
DW
108 * @param bool $skippermissions During signup we need to set this before the user is setup for capability checks.
109 * @return bool
1dca8d1a 110 */
28b592d5 111 public static function link_login($userinfo, $issuer, $userid = false, $skippermissions = false) {
1dca8d1a
DW
112 global $USER;
113
114 if ($userid === false) {
115 $userid = $USER->id;
116 }
117
95dd5e3b 118 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
4f705f5d
DW
119 throw new moodle_exception('alreadylinked', 'auth_oauth2');
120 }
121
1dca8d1a
DW
122 if (\core\session\manager::is_loggedinas()) {
123 throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
124 }
125
126 $context = context_user::instance($userid);
28b592d5
DW
127 if (!$skippermissions) {
128 require_capability('auth/oauth2:managelinkedlogins', $context);
129 }
1dca8d1a 130
1dca8d1a
DW
131 $record = new stdClass();
132 $record->issuerid = $issuer->get('id');
133 $record->username = $userinfo['username'];
1dca8d1a
DW
134 $record->userid = $userid;
135 $existing = linked_login::get_record((array)$record);
136 if ($existing) {
28b592d5
DW
137 $existing->set('confirmtoken', '');
138 $existing->update();
1dca8d1a
DW
139 return $existing;
140 }
28b592d5
DW
141 $record->email = $userinfo['email'];
142 $record->confirmtoken = '';
a647e7d7 143 $record->confirmtokenexpires = 0;
1dca8d1a
DW
144 $linkedlogin = new linked_login(0, $record);
145 return $linkedlogin->create();
146 }
147
28b592d5
DW
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) {
28b592d5
DW
157 $record = new stdClass();
158 $record->issuerid = $issuer->get('id');
159 $record->username = $userinfo['username'];
160 $record->userid = $userid;
95dd5e3b 161 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
4f705f5d 162 throw new moodle_exception('alreadylinked', 'auth_oauth2');
28b592d5
DW
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 // Remove data parameter just in case it was included in the confirmation so we can add it manually later.
196 $data->link = $confirmationurl->out();
197
198 $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
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 * 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');
254
95dd5e3b 255 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
4f705f5d
DW
256 throw new moodle_exception('alreadylinked', 'auth_oauth2');
257 }
258
28b592d5
DW
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);
269
270 $user->password = '';
271 // This user is not confirmed.
272 $user->confirmed = 0;
273
274 $user->id = user_create_user($user, false, true);
275
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;
284
285 $linkedlogin = new linked_login(0, $record);
286 $linkedlogin->create();
287
288 // Construct the email.
289 $site = get_site();
290 $supportuser = \core_user::get_support_user();
291 $user = get_complete_user_data('id', $user->id);
292
293 $data = new stdClass();
294 $data->fullname = fullname($user);
295 $data->sitename = format_string($site->fullname);
296 $data->admin = generate_email_signoff();
297
298 $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
299
300 $params = [
301 'token' => $user->secret,
302 'username' => $userinfo['username']
303 ];
304 $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
305
306 $data->link = $confirmationurl->out();
307
308 $message = get_string('confirmaccountemail', 'auth_oauth2', $data);
309 $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
310
311 $user->mailformat = 1; // Always send HTML version as well.
312
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 }
317
1dca8d1a
DW
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');
329
330 if (\core\session\manager::is_loggedinas()) {
331 throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
332 }
333
334 $context = context_user::instance($userid);
335 require_capability('auth/oauth2:managelinkedlogins', $context);
336
337 $login->delete();
338 }
e0abc2e4
DW
339
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;
348
349 $userid = $event->objectid;
350
351 return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
352 }
1dca8d1a 353}