Commit | Line | Data |
---|---|---|
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 | */ | |
24 | namespace auth_oauth2; | |
25 | ||
26 | use context_user; | |
27 | use stdClass; | |
28 | use moodle_exception; | |
28b592d5 | 29 | use moodle_url; |
1dca8d1a DW |
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 | ||
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 | } |