a96ebc64709dc4623bfa878df008e0dd17081715
[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      * List linked logins
44      *
45      * Requires auth/oauth2:managelinkedlogins capability at the user context.
46      *
47      * @param int $userid (defaults to $USER->id)
48      * @return boolean
49      */
50     public static function get_linked_logins($userid = false) {
51         global $USER;
53         if ($userid === false) {
54             $userid = $USER->id;
55         }
57         if (\core\session\manager::is_loggedinas()) {
58             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
59         }
61         $context = context_user::instance($userid);
62         require_capability('auth/oauth2:managelinkedlogins', $context);
64         return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']);
65     }
67     /**
68      * See if there is a match for this username and issuer in the linked_login table.
69      *
70      * @param string $username as returned from an oauth client.
71      * @param \core\oauth2\issuer $issuer
72      * @return stdClass User record if found.
73      */
74     public static function match_username_to_user($username, $issuer) {
75         $params = [
76             'issuerid' => $issuer->get('id'),
77             'username' => $username
78         ];
79         $result = linked_login::get_record($params);
81         if ($result) {
82             $user = \core_user::get_user($result->get('userid'));
83             if (!empty($user) && !$user->deleted) {
84                 return $result;
85             }
86         }
87         return false;
88     }
90     /**
91      * Link a login to this account.
92      *
93      * Requires auth/oauth2:managelinkedlogins capability at the user context.
94      *
95      * @param array $userinfo as returned from an oauth client.
96      * @param \core\oauth2\issuer $issuer
97      * @param int $userid (defaults to $USER->id)
98      * @param bool $skippermissions During signup we need to set this before the user is setup for capability checks.
99      * @return bool
100      */
101     public static function link_login($userinfo, $issuer, $userid = false, $skippermissions = false) {
102         global $USER;
104         if ($userid === false) {
105             $userid = $USER->id;
106         }
108         if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
109             throw new moodle_exception('alreadylinked', 'auth_oauth2');
110         }
112         if (\core\session\manager::is_loggedinas()) {
113             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
114         }
116         $context = context_user::instance($userid);
117         if (!$skippermissions) {
118             require_capability('auth/oauth2:managelinkedlogins', $context);
119         }
121         $record = new stdClass();
122         $record->issuerid = $issuer->get('id');
123         $record->username = $userinfo['username'];
124         $record->userid = $userid;
125         $existing = linked_login::get_record((array)$record);
126         if ($existing) {
127             $existing->set('confirmtoken', '');
128             $existing->update();
129             return $existing;
130         }
131         $record->email = $userinfo['email'];
132         $record->confirmtoken = '';
133         $record->confirmtokenexpires = 0;
134         $linkedlogin = new linked_login(0, $record);
135         return $linkedlogin->create();
136     }
138     /**
139      * Send an email with a link to confirm linking this account.
140      *
141      * @param array $userinfo as returned from an oauth client.
142      * @param \core\oauth2\issuer $issuer
143      * @param int $userid (defaults to $USER->id)
144      * @return bool
145      */
146     public static function send_confirm_link_login_email($userinfo, $issuer, $userid) {
147         $record = new stdClass();
148         $record->issuerid = $issuer->get('id');
149         $record->username = $userinfo['username'];
150         $record->userid = $userid;
151         if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
152             throw new moodle_exception('alreadylinked', 'auth_oauth2');
153         }
154         $record->email = $userinfo['email'];
155         $record->confirmtoken = random_string(32);
156         $expires = new \DateTime('NOW');
157         $expires->add(new \DateInterval('PT30M'));
158         $record->confirmtokenexpires = $expires->getTimestamp();
160         $linkedlogin = new linked_login(0, $record);
161         $linkedlogin->create();
163         // Construct the email.
164         $site = get_site();
165         $supportuser = \core_user::get_support_user();
166         $user = get_complete_user_data('id', $userid);
168         $data = new stdClass();
169         $data->fullname = fullname($user);
170         $data->sitename  = format_string($site->fullname);
171         $data->admin     = generate_email_signoff();
172         $data->issuername = format_string($issuer->get('name'));
173         $data->linkedemail = format_string($linkedlogin->get('email'));
175         $subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname));
177         $params = [
178             'token' => $linkedlogin->get('confirmtoken'),
179             'userid' => $userid,
180             'username' => $userinfo['username'],
181             'issuerid' => $issuer->get('id'),
182         ];
183         $confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params);
185         // Remove data parameter just in case it was included in the confirmation so we can add it manually later.
186         $data->link = $confirmationurl->out();
188         $message     = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
189         $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
191         $user->mailformat = 1;  // Always send HTML version as well.
193         // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
194         return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
195     }
197     /**
198      * Look for a waiting confirmation token, and if we find a match - confirm it.
199      *
200      * @param int $userid
201      * @param string $username
202      * @param int $issuerid
203      * @param string $token
204      * @return boolean True if we linked.
205      */
206     public static function confirm_link_login($userid, $username, $issuerid, $token) {
207         if (empty($token) || empty($userid) || empty($issuerid) || empty($username)) {
208             return false;
209         }
210         $params = [
211             'userid' => $userid,
212             'username' => $username,
213             'issuerid' => $issuerid,
214             'confirmtoken' => $token,
215         ];
217         $login = linked_login::get_record($params);
218         if (empty($login)) {
219             return false;
220         }
221         $expires = $login->get('confirmtokenexpires');
222         if (time() > $expires) {
223             $login->delete();
224             return;
225         }
226         $login->set('confirmtokenexpires', 0);
227         $login->set('confirmtoken', '');
228         $login->update();
229         return true;
230     }
232     /**
233      * Send an email with a link to confirm creating this account.
234      *
235      * @param array $userinfo as returned from an oauth client.
236      * @param \core\oauth2\issuer $issuer
237      * @param int $userid (defaults to $USER->id)
238      * @return bool
239      */
240     public static function send_confirm_account_email($userinfo, $issuer) {
241         global $CFG, $DB;
242         require_once($CFG->dirroot.'/user/profile/lib.php');
243         require_once($CFG->dirroot.'/user/lib.php');
245         if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
246             throw new moodle_exception('alreadylinked', 'auth_oauth2');
247         }
249         $user = new stdClass();
250         $user->username = $userinfo['username'];
251         $user->email = $userinfo['email'];
252         $user->auth = 'oauth2';
253         $user->mnethostid = $CFG->mnet_localhost_id;
254         $user->lastname = isset($userinfo['lastname']) ? $userinfo['lastname'] : '';
255         $user->firstname = isset($userinfo['firstname']) ? $userinfo['firstname'] : '';
256         $user->url = isset($userinfo['url']) ? $userinfo['url'] : '';
257         $user->alternatename = isset($userinfo['alternatename']) ? $userinfo['alternatename'] : '';
258         $user->secret = random_string(15);
260         $user->password = '';
261         // This user is not confirmed.
262         $user->confirmed = 0;
264         $user->id = user_create_user($user, false, true);
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;
275         $linkedlogin = new linked_login(0, $record);
276         $linkedlogin->create();
278         // Construct the email.
279         $site = get_site();
280         $supportuser = \core_user::get_support_user();
281         $user = get_complete_user_data('id', $user->id);
283         $data = new stdClass();
284         $data->fullname = fullname($user);
285         $data->sitename  = format_string($site->fullname);
286         $data->admin     = generate_email_signoff();
288         $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
290         $params = [
291             'token' => $user->secret,
292             'username' => $userinfo['username']
293         ];
294         $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
296         $data->link = $confirmationurl->out();
298         $message     = get_string('confirmaccountemail', 'auth_oauth2', $data);
299         $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
301         $user->mailformat = 1;  // Always send HTML version as well.
303         // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
304         email_to_user($user, $supportuser, $subject, $message, $messagehtml);
305         return $user;
306     }
308     /**
309      * Delete linked login
310      *
311      * Requires auth/oauth2:managelinkedlogins capability at the user context.
312      *
313      * @param int $linkedloginid
314      * @return boolean
315      */
316     public static function delete_linked_login($linkedloginid) {
317         $login = new linked_login($linkedloginid);
318         $userid = $login->get('userid');
320         if (\core\session\manager::is_loggedinas()) {
321             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
322         }
324         $context = context_user::instance($userid);
325         require_capability('auth/oauth2:managelinkedlogins', $context);
327         $login->delete();
328     }
330     /**
331      * Delete linked logins for a user.
332      *
333      * @param \core\event\user_deleted $event
334      * @return boolean
335      */
336     public static function user_deleted(\core\event\user_deleted $event) {
337         global $DB;
339         $userid = $event->objectid;
341         return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
342     }