MDL-59510 core_oauth2: add autorefresh mode to oauth2\client
[moodle.git] / lib / classes / oauth2 / api.php
CommitLineData
60237253
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 endpoints from the DB.
19 *
29911249 20 * @package core
60237253
DW
21 * @copyright 2017 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace core\oauth2;
25
29911249
DW
26defined('MOODLE_INTERNAL') || die();
27
60237253
DW
28require_once($CFG->libdir . '/filelib.php');
29
30use context_system;
31use curl;
32use stdClass;
33use moodle_exception;
34use moodle_url;
35
60237253
DW
36
37/**
38 * Static list of api methods for system oauth2 configuration.
39 *
40 * @copyright 2017 Damyon Wiese
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 */
43class api {
44
f9f243f9 45 /**
fa6cd89b 46 * Build a google ready OAuth 2 service.
bd0b9873 47 * @return \core\oauth2\issuer
f9f243f9 48 */
fa6cd89b 49 private static function init_google() {
60237253
DW
50 $record = (object) [
51 'name' => 'Google',
52 'image' => 'https://accounts.google.com/favicon.ico',
3e3e120d 53 'baseurl' => 'https://accounts.google.com/',
485a22fc 54 'loginparamsoffline' => 'access_type=offline&prompt=consent',
60237253
DW
55 'showonloginpage' => true
56 ];
57
58 $issuer = new issuer(0, $record);
fa6cd89b
TR
59 return $issuer;
60 }
61
62 /**
63 * Create endpoints for google issuers.
64 * @param issuer $issuer issuer the endpoints should be created for.
65 * @return mixed
66 * @throws \coding_exception
67 * @throws \core\invalid_persistent_exception
68 */
69 private static function create_endpoints_for_google($issuer) {
60237253
DW
70
71 $record = (object) [
72 'issuerid' => $issuer->get('id'),
73 'name' => 'discovery_endpoint',
74 'url' => 'https://accounts.google.com/.well-known/openid-configuration'
75 ];
76 $endpoint = new endpoint(0, $record);
77 $endpoint->create();
dc4b5685 78 return $issuer;
ddf65b8c
DW
79 }
80
f9f243f9 81 /**
fa6cd89b 82 * Build a facebook ready OAuth 2 service.
bd0b9873 83 * @return \core\oauth2\issuer
f9f243f9 84 */
fa6cd89b 85 private static function init_facebook() {
ddf65b8c
DW
86 // Facebook is a custom setup.
87 $record = (object) [
88 'name' => 'Facebook',
4b0cf053 89 'image' => 'https://facebookbrand.com/wp-content/uploads/2016/05/flogo_rgb_hex-brc-site-250.png',
dc4b5685 90 'baseurl' => '',
ddf65b8c
DW
91 'loginscopes' => 'public_profile email',
92 'loginscopesoffline' => 'public_profile email',
93 'showonloginpage' => true
94 ];
95
96 $issuer = new issuer(0, $record);
fa6cd89b
TR
97 return $issuer;
98 }
ddf65b8c 99
fa6cd89b
TR
100 /**
101 * Create endpoints for facebook issuers.
102 * @param issuer $issuer issuer the endpoints should be created for.
103 * @return mixed
104 * @throws \coding_exception
105 * @throws \core\invalid_persistent_exception
106 */
107 private static function create_endpoints_for_facebook($issuer) {
7766dbed
JP
108 // The Facebook API version.
109 $apiversion = '2.12';
110 // The Graph API URL.
111 $graphurl = 'https://graph.facebook.com/v' . $apiversion;
112 // User information fields that we want to fetch.
113 $infofields = [
114 'id',
115 'first_name',
116 'last_name',
117 'link',
118 'picture.type(large)',
119 'name',
120 'email',
121 ];
ddf65b8c 122 $endpoints = [
7766dbed
JP
123 'authorization_endpoint' => sprintf('https://www.facebook.com/v%s/dialog/oauth', $apiversion),
124 'token_endpoint' => $graphurl . '/oauth/access_token',
125 'userinfo_endpoint' => $graphurl . '/me?fields=' . implode(',', $infofields)
ddf65b8c 126 ];
60237253 127
ddf65b8c
DW
128 foreach ($endpoints as $name => $url) {
129 $record = (object) [
130 'issuerid' => $issuer->get('id'),
131 'name' => $name,
132 'url' => $url
133 ];
134 $endpoint = new endpoint(0, $record);
135 $endpoint->create();
136 }
137
138 // Create the field mappings.
139 $mapping = [
140 'name' => 'alternatename',
141 'last_name' => 'lastname',
142 'email' => 'email',
ddf65b8c
DW
143 'first_name' => 'firstname',
144 'picture-data-url' => 'picture',
145 'link' => 'url',
146 ];
147 foreach ($mapping as $external => $internal) {
148 $record = (object) [
149 'issuerid' => $issuer->get('id'),
150 'externalfield' => $external,
151 'internalfield' => $internal
152 ];
153 $userfieldmapping = new user_field_mapping(0, $record);
154 $userfieldmapping->create();
155 }
dc4b5685 156 return $issuer;
ddf65b8c
DW
157 }
158
f9f243f9 159 /**
fa6cd89b 160 * Build a microsoft ready OAuth 2 service.
bd0b9873 161 * @return \core\oauth2\issuer
f9f243f9 162 */
fa6cd89b 163 private static function init_microsoft() {
8445556b 164 // Microsoft is a custom setup.
60237253
DW
165 $record = (object) [
166 'name' => 'Microsoft',
167 'image' => 'https://www.microsoft.com/favicon.ico',
dc4b5685 168 'baseurl' => '',
485a22fc
DW
169 'loginscopes' => 'openid profile email user.read',
170 'loginscopesoffline' => 'openid profile email user.read offline_access',
60237253
DW
171 'showonloginpage' => true
172 ];
173
174 $issuer = new issuer(0, $record);
fa6cd89b
TR
175 return $issuer;
176 }
177
178 /**
179 * Create endpoints for microsoft issuers.
180 * @param issuer $issuer issuer the endpoints should be created for.
181 * @return mixed
182 * @throws \coding_exception
183 * @throws \core\invalid_persistent_exception
184 */
185 private static function create_endpoints_for_microsoft($issuer) {
60237253 186
8445556b
DW
187 $endpoints = [
188 'authorization_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
189 'token_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
190 'userinfo_endpoint' => 'https://graph.microsoft.com/v1.0/me/',
191 'userpicture_endpoint' => 'https://graph.microsoft.com/v1.0/me/photo/$value',
60237253 192 ];
60237253 193
8445556b
DW
194 foreach ($endpoints as $name => $url) {
195 $record = (object) [
196 'issuerid' => $issuer->get('id'),
197 'name' => $name,
198 'url' => $url
199 ];
200 $endpoint = new endpoint(0, $record);
201 $endpoint->create();
202 }
60237253 203
8445556b
DW
204 // Create the field mappings.
205 $mapping = [
206 'givenName' => 'firstname',
207 'surname' => 'lastname',
7f158660 208 'userPrincipalName' => 'email',
8445556b
DW
209 'displayName' => 'alternatename',
210 'officeLocation' => 'address',
9165e838 211 'mobilePhone' => 'phone1',
8445556b 212 'preferredLanguage' => 'lang'
60237253 213 ];
8445556b
DW
214 foreach ($mapping as $external => $internal) {
215 $record = (object) [
216 'issuerid' => $issuer->get('id'),
217 'externalfield' => $external,
218 'internalfield' => $internal
219 ];
220 $userfieldmapping = new user_field_mapping(0, $record);
221 $userfieldmapping->create();
222 }
dc4b5685 223 return $issuer;
ddf65b8c
DW
224 }
225
3e3e120d
TR
226 /**
227 * Build a nextcloud ready OAuth 2 service.
228 * @return \core\oauth2\issuer
229 */
230 private static function init_nextcloud() {
231 // Nextcloud has a custom baseurl. Thus, the creation of endpoints has to be done later.
232 $record = (object) [
233 'name' => 'Nextcloud',
234 'image' => 'https://nextcloud.com/wp-content/themes/next/assets/img/common/favicon.png?x16328',
235 'basicauth' => 1,
236 ];
237
238 $issuer = new issuer(0, $record);
239
240 return $issuer;
241 }
242
243 /**
244 * Create endpoints for nextcloud issuers.
245 * @param issuer $issuer issuer the endpoints should be created for.
246 * @return mixed
247 * @throws \coding_exception
248 * @throws \core\invalid_persistent_exception
249 */
250 private static function create_endpoints_for_nextcloud($issuer) {
251 $baseurl = $issuer->get('baseurl');
252 // Add trailing slash to baseurl, if needed.
253 if (substr($baseurl, -1) !== '/') {
254 $baseurl .= '/';
255 }
256
257 $endpoints = [
258 // Baseurl will be prepended later.
259 'authorization_endpoint' => 'index.php/apps/oauth2/authorize',
260 'token_endpoint' => 'index.php/apps/oauth2/api/v1/token',
261 'userinfo_endpoint' => 'ocs/v2.php/cloud/user?format=json',
262 'webdav_endpoint' => 'remote.php/webdav/',
263 'ocs_endpoint' => 'ocs/v1.php/apps/files_sharing/api/v1/shares',
264 ];
265
266 foreach ($endpoints as $name => $url) {
267 $record = (object) [
268 'issuerid' => $issuer->get('id'),
269 'name' => $name,
270 'url' => $baseurl . $url,
271 ];
272 $endpoint = new \core\oauth2\endpoint(0, $record);
273 $endpoint->create();
274 }
275
276 // Create the field mappings.
277 $mapping = [
278 'ocs-data-email' => 'email',
279 'ocs-data-id' => 'username',
280 ];
281 foreach ($mapping as $external => $internal) {
282 $record = (object) [
283 'issuerid' => $issuer->get('id'),
284 'externalfield' => $external,
285 'internalfield' => $internal
286 ];
287 $userfieldmapping = new \core\oauth2\user_field_mapping(0, $record);
288 $userfieldmapping->create();
289 }
290 }
291
29911249 292 /**
fa6cd89b
TR
293 * Initializes a record for one of the standard issuers to be displayed in the settings.
294 * The issuer is not yet created in the database.
3e3e120d 295 * @param string $type One of google, facebook, microsoft, nextcloud
fa6cd89b
TR
296 * @return \core\oauth2\issuer
297 */
298 public static function init_standard_issuer($type) {
299 require_capability('moodle/site:config', context_system::instance());
300 if ($type == 'google') {
301 return self::init_google();
302 } else if ($type == 'microsoft') {
303 return self::init_microsoft();
304 } else if ($type == 'facebook') {
305 return self::init_facebook();
3e3e120d
TR
306 } else if ($type == 'nextcloud') {
307 return self::init_nextcloud();
fa6cd89b
TR
308 } else {
309 throw new moodle_exception('OAuth 2 service type not recognised: ' . $type);
310 }
311 }
312
313 /**
314 * Create endpoints for standard issuers, based on the issuer created from submitted data.
3e3e120d 315 * @param string $type One of google, facebook, microsoft, nextcloud
fa6cd89b
TR
316 * @param issuer $issuer issuer the endpoints should be created for.
317 * @return \core\oauth2\issuer
318 */
319 public static function create_endpoints_for_standard_issuer($type, $issuer) {
320 require_capability('moodle/site:config', context_system::instance());
321 if ($type == 'google') {
3559677c
JD
322 $issuer = self::create_endpoints_for_google($issuer);
323 self::discover_endpoints($issuer);
324 return $issuer;
fa6cd89b
TR
325 } else if ($type == 'microsoft') {
326 return self::create_endpoints_for_microsoft($issuer);
327 } else if ($type == 'facebook') {
328 return self::create_endpoints_for_facebook($issuer);
3e3e120d
TR
329 } else if ($type == 'nextcloud') {
330 return self::create_endpoints_for_nextcloud($issuer);
fa6cd89b
TR
331 } else {
332 throw new moodle_exception('OAuth 2 service type not recognised: ' . $type);
333 }
334 }
335
336 /**
3e3e120d
TR
337 * Create one of the standard issuers.
338 * @param string $type One of google, facebook, microsoft, or nextcloud
339 * @param string|false $baseurl Baseurl (only required for nextcloud)
29911249
DW
340 * @return \core\oauth2\issuer
341 */
3e3e120d 342 public static function create_standard_issuer($type, $baseurl = false) {
dc4b5685
DW
343 require_capability('moodle/site:config', context_system::instance());
344 if ($type == 'google') {
fa6cd89b
TR
345 $issuer = self::init_google();
346 $issuer->create();
347 return self::create_endpoints_for_google($issuer);
dc4b5685 348 } else if ($type == 'microsoft') {
fa6cd89b
TR
349 $issuer = self::init_microsoft();
350 $issuer->create();
351 return self::create_endpoints_for_microsoft($issuer);
dc4b5685 352 } else if ($type == 'facebook') {
fa6cd89b
TR
353 $issuer = self::init_facebook();
354 $issuer->create();
355 return self::create_endpoints_for_facebook($issuer);
3e3e120d
TR
356 } else if ($type == 'nextcloud') {
357 if (!$baseurl) {
358 throw new moodle_exception('Nextcloud service type requires the baseurl parameter.');
359 }
360 $issuer = self::init_nextcloud();
361 $issuer->set('baseurl', $baseurl);
362 $issuer->create();
363 return self::create_endpoints_for_nextcloud($issuer);
dc4b5685
DW
364 } else {
365 throw new moodle_exception('OAuth 2 service type not recognised: ' . $type);
366 }
60237253
DW
367 }
368
fa6cd89b 369
f9f243f9
DW
370 /**
371 * List all the issuers, ordered by the sortorder field
bd0b9873 372 * @return \core\oauth2\issuer[]
f9f243f9 373 */
60237253
DW
374 public static function get_all_issuers() {
375 return issuer::get_records([], 'sortorder');
376 }
377
f9f243f9
DW
378 /**
379 * Get a single issuer by id.
380 *
381 * @param int $id
bd0b9873 382 * @return \core\oauth2\issuer
f9f243f9 383 */
60237253
DW
384 public static function get_issuer($id) {
385 return new issuer($id);
386 }
387
f9f243f9
DW
388 /**
389 * Get a single endpoint by id.
390 *
391 * @param int $id
bd0b9873 392 * @return \core\oauth2\endpoint
f9f243f9 393 */
8445556b
DW
394 public static function get_endpoint($id) {
395 return new endpoint($id);
396 }
397
f9f243f9
DW
398 /**
399 * Get a single user field mapping by id.
400 *
401 * @param int $id
bd0b9873 402 * @return \core\oauth2\user_field_mapping
f9f243f9 403 */
8445556b
DW
404 public static function get_user_field_mapping($id) {
405 return new user_field_mapping($id);
406 }
407
f9f243f9
DW
408 /**
409 * Get the system account for an installed OAuth service.
410 * Never ever ever expose this to a webservice because it contains the refresh token which grants API access.
411 *
72fd103a 412 * @param \core\oauth2\issuer $issuer
7927138d 413 * @return system_account|false
f9f243f9 414 */
60237253
DW
415 public static function get_system_account(issuer $issuer) {
416 return system_account::get_record(['issuerid' => $issuer->get('id')]);
417 }
418
f9f243f9
DW
419 /**
420 * Get the full list of system scopes required by an oauth issuer.
421 * This includes the list required for login as well as any scopes injected by the oauth2_system_scopes callback in plugins.
422 *
29911249 423 * @param \core\oauth2\issuer $issuer
f9f243f9
DW
424 * @return string
425 */
237fd80c
DW
426 public static function get_system_scopes_for_issuer($issuer) {
427 $scopes = $issuer->get('loginscopesoffline');
428
429 $pluginsfunction = get_plugins_with_function('oauth2_system_scopes', 'lib.php');
430 foreach ($pluginsfunction as $plugintype => $plugins) {
431 foreach ($plugins as $pluginfunction) {
432 // Get additional scopes from the plugin.
433 $pluginscopes = $pluginfunction($issuer);
434 if (empty($pluginscopes)) {
435 continue;
436 }
437
438 // Merge the additional scopes with the existing ones.
439 $additionalscopes = explode(' ', $pluginscopes);
440
441 foreach ($additionalscopes as $scope) {
442 if (!empty($scope)) {
443 if (strpos(' ' . $scopes . ' ', ' ' . $scope . ' ') === false) {
444 $scopes .= ' ' . $scope;
445 }
446 }
447 }
448 }
449 }
450
451 return $scopes;
452 }
453
f9f243f9
DW
454 /**
455 * Get an authenticated oauth2 client using the system account.
456 * This call uses the refresh token to get an access token.
457 *
bd0b9873
JD
458 * @param \core\oauth2\issuer $issuer
459 * @return \core\oauth2\client|false An authenticated client (or false if the token could not be upgraded)
460 * @throws moodle_exception Request for token upgrade failed for technical reasons
f9f243f9 461 */
60237253 462 public static function get_system_oauth_client(issuer $issuer) {
237fd80c
DW
463 $systemaccount = self::get_system_account($issuer);
464 if (empty($systemaccount)) {
465 return false;
466 }
467 // Get all the scopes!
468 $scopes = self::get_system_scopes_for_issuer($issuer);
469
470 $client = new \core\oauth2\client($issuer, null, $scopes, true);
471
472 if (!$client->is_logged_in()) {
931c0234 473 if (!$client->upgrade_refresh_token($systemaccount)) {
237fd80c
DW
474 return false;
475 }
476 }
477 return $client;
60237253
DW
478 }
479
f9f243f9
DW
480 /**
481 * Get an authenticated oauth2 client using the current user account.
482 * This call does the redirect dance back to the current page after authentication.
483 *
bd0b9873 484 * @param \core\oauth2\issuer $issuer The desired OAuth issuer
29911249 485 * @param moodle_url $currenturl The url to the current page.
f9f243f9 486 * @param string $additionalscopes The additional scopes required for authorization.
8b098533 487 * @param bool $autorefresh Should the client support the use of refresh tokens to persist access across sessions.
bd0b9873 488 * @return \core\oauth2\client
f9f243f9 489 */
8b098533
JD
490 public static function get_user_oauth_client(issuer $issuer, moodle_url $currenturl, $additionalscopes = '',
491 $autorefresh = false) {
492 $client = new \core\oauth2\client($issuer, $currenturl, $additionalscopes, false, $autorefresh);
60237253 493
60237253
DW
494 return $client;
495 }
496
f9f243f9
DW
497 /**
498 * Get the list of defined endpoints for this OAuth issuer
499 *
bd0b9873
JD
500 * @param \core\oauth2\issuer $issuer The desired OAuth issuer
501 * @return \core\oauth2\endpoint[]
f9f243f9 502 */
60237253 503 public static function get_endpoints(issuer $issuer) {
60237253
DW
504 return endpoint::get_records(['issuerid' => $issuer->get('id')]);
505 }
506
f9f243f9
DW
507 /**
508 * Get the list of defined mapping from OAuth user fields to moodle user fields.
509 *
bd0b9873
JD
510 * @param \core\oauth2\issuer $issuer The desired OAuth issuer
511 * @return \core\oauth2\user_field_mapping[]
f9f243f9 512 */
8445556b
DW
513 public static function get_user_field_mappings(issuer $issuer) {
514 return user_field_mapping::get_records(['issuerid' => $issuer->get('id')]);
515 }
516
f9f243f9
DW
517 /**
518 * Guess an image from the discovery URL.
519 *
bd0b9873 520 * @param \core\oauth2\issuer $issuer The desired OAuth issuer
f9f243f9 521 */
60237253 522 protected static function guess_image($issuer) {
02cc1ecd 523 if (empty($issuer->get('image')) && !empty($issuer->get('baseurl'))) {
d0298413 524 $baseurl = parse_url($issuer->get('baseurl'));
60237253
DW
525 $imageurl = $baseurl['scheme'] . '://' . $baseurl['host'] . '/favicon.ico';
526 $issuer->set('image', $imageurl);
527 $issuer->update();
528 }
529 }
530
531 /**
ddf65b8c 532 * If the discovery endpoint exists for this issuer, try and determine the list of valid endpoints.
60237253
DW
533 *
534 * @param issuer $issuer
535 * @return int The number of discovered services.
536 */
537 protected static function discover_endpoints($issuer) {
538 $curl = new curl();
539
ddf65b8c 540 if (empty($issuer->get('baseurl'))) {
60237253
DW
541 return 0;
542 }
543
544 $url = $issuer->get_endpoint_url('discovery');
545 if (!$url) {
485a22fc 546 $url = $issuer->get('baseurl') . '/.well-known/openid-configuration';
60237253
DW
547 }
548
ddf65b8c 549 if (!$json = $curl->get($url)) {
60237253
DW
550 $msg = 'Could not discover end points for identity issuer' . $issuer->get('name');
551 throw new moodle_exception($msg);
552 }
553
554 if ($msg = $curl->error) {
555 throw new moodle_exception('Could not discover service endpoints: ' . $msg);
556 }
557
558 $info = json_decode($json);
559 if (empty($info)) {
560 $msg = 'Could not discover end points for identity issuer' . $issuer->get('name');
561 throw new moodle_exception($msg);
562 }
563
564 foreach (endpoint::get_records(['issuerid' => $issuer->get('id')]) as $endpoint) {
565 if ($endpoint->get('name') != 'discovery_endpoint') {
566 $endpoint->delete();
567 }
568 }
569
570 foreach ($info as $key => $value) {
571 if (substr_compare($key, '_endpoint', - strlen('_endpoint')) === 0) {
572 $record = new stdClass();
573 $record->issuerid = $issuer->get('id');
574 $record->name = $key;
575 $record->url = $value;
576
577 $endpoint = new endpoint(0, $record);
578 $endpoint->create();
579 }
580
581 if ($key == 'scopes_supported') {
582 $issuer->set('scopessupported', implode(' ', $value));
583 $issuer->update();
584 }
585 }
586
8445556b 587 // We got to here - must be a decent OpenID connect service. Add the default user field mapping list.
ddf65b8c
DW
588 foreach (user_field_mapping::get_records(['issuerid' => $issuer->get('id')]) as $userfieldmapping) {
589 $userfieldmapping->delete();
590 }
8445556b
DW
591
592 // Create the field mappings.
593 $mapping = [
594 'given_name' => 'firstname',
595 'middle_name' => 'middlename',
596 'family_name' => 'lastname',
597 'email' => 'email',
8445556b
DW
598 'website' => 'url',
599 'nickname' => 'alternatename',
600 'picture' => 'picture',
601 'address' => 'address',
9165e838 602 'phone' => 'phone1',
8445556b
DW
603 'locale' => 'lang'
604 ];
605 foreach ($mapping as $external => $internal) {
606 $record = (object) [
607 'issuerid' => $issuer->get('id'),
608 'externalfield' => $external,
609 'internalfield' => $internal
610 ];
611 $userfieldmapping = new user_field_mapping(0, $record);
612 $userfieldmapping->create();
613 }
614
60237253
DW
615 return endpoint::count_records(['issuerid' => $issuer->get('id')]);
616 }
617
f9f243f9
DW
618 /**
619 * Take the data from the mform and update the issuer.
620 *
621 * @param stdClass $data
bd0b9873 622 * @return \core\oauth2\issuer
f9f243f9 623 */
60237253
DW
624 public static function update_issuer($data) {
625 require_capability('moodle/site:config', context_system::instance());
626 $issuer = new issuer(0, $data);
627
628 // Will throw exceptions on validation failures.
629 $issuer->update();
630
631 // Perform service discovery.
632 self::discover_endpoints($issuer);
633 self::guess_image($issuer);
8445556b 634 return $issuer;
60237253
DW
635 }
636
f9f243f9
DW
637 /**
638 * Take the data from the mform and create the issuer.
639 *
640 * @param stdClass $data
bd0b9873 641 * @return \core\oauth2\issuer
f9f243f9 642 */
60237253
DW
643 public static function create_issuer($data) {
644 require_capability('moodle/site:config', context_system::instance());
645 $issuer = new issuer(0, $data);
646
647 // Will throw exceptions on validation failures.
648 $issuer->create();
649
650 // Perform service discovery.
651 self::discover_endpoints($issuer);
652 self::guess_image($issuer);
8445556b
DW
653 return $issuer;
654 }
655
f9f243f9
DW
656 /**
657 * Take the data from the mform and update the endpoint.
658 *
659 * @param stdClass $data
bd0b9873 660 * @return \core\oauth2\endpoint
f9f243f9 661 */
8445556b
DW
662 public static function update_endpoint($data) {
663 require_capability('moodle/site:config', context_system::instance());
664 $endpoint = new endpoint(0, $data);
665
666 // Will throw exceptions on validation failures.
667 $endpoint->update();
668
669 return $endpoint;
670 }
671
f9f243f9
DW
672 /**
673 * Take the data from the mform and create the endpoint.
674 *
675 * @param stdClass $data
bd0b9873 676 * @return \core\oauth2\endpoint
f9f243f9 677 */
8445556b
DW
678 public static function create_endpoint($data) {
679 require_capability('moodle/site:config', context_system::instance());
680 $endpoint = new endpoint(0, $data);
681
682 // Will throw exceptions on validation failures.
683 $endpoint->create();
684 return $endpoint;
685 }
686
f9f243f9
DW
687 /**
688 * Take the data from the mform and update the user field mapping.
689 *
690 * @param stdClass $data
bd0b9873 691 * @return \core\oauth2\user_field_mapping
f9f243f9 692 */
8445556b
DW
693 public static function update_user_field_mapping($data) {
694 require_capability('moodle/site:config', context_system::instance());
695 $userfieldmapping = new user_field_mapping(0, $data);
696
697 // Will throw exceptions on validation failures.
698 $userfieldmapping->update();
699
700 return $userfieldmapping;
701 }
702
f9f243f9
DW
703 /**
704 * Take the data from the mform and create the user field mapping.
705 *
706 * @param stdClass $data
bd0b9873 707 * @return \core\oauth2\user_field_mapping
f9f243f9 708 */
8445556b
DW
709 public static function create_user_field_mapping($data) {
710 require_capability('moodle/site:config', context_system::instance());
711 $userfieldmapping = new user_field_mapping(0, $data);
712
713 // Will throw exceptions on validation failures.
714 $userfieldmapping->create();
715 return $userfieldmapping;
60237253
DW
716 }
717
718 /**
719 * Reorder this identity issuer.
720 *
721 * Requires moodle/site:config capability at the system context.
722 *
723 * @param int $id The id of the identity issuer to move.
724 * @return boolean
725 */
726 public static function move_up_issuer($id) {
727 require_capability('moodle/site:config', context_system::instance());
728 $current = new issuer($id);
729
730 $sortorder = $current->get('sortorder');
731 if ($sortorder == 0) {
732 return false;
733 }
734
735 $sortorder = $sortorder - 1;
736 $current->set('sortorder', $sortorder);
737
738 $filters = array('sortorder' => $sortorder);
739 $children = issuer::get_records($filters, 'id');
740 foreach ($children as $needtoswap) {
741 $needtoswap->set('sortorder', $sortorder + 1);
742 $needtoswap->update();
743 }
744
745 // OK - all set.
746 $result = $current->update();
747
748 return $result;
749 }
750
f9f243f9
DW
751 /**
752 * Reorder this identity issuer.
753 *
754 * Requires moodle/site:config capability at the system context.
755 *
756 * @param int $id The id of the identity issuer to move.
757 * @return boolean
758 */
60237253
DW
759 public static function move_down_issuer($id) {
760 require_capability('moodle/site:config', context_system::instance());
761 $current = new issuer($id);
762
763 $max = issuer::count_records();
764 if ($max > 0) {
765 $max--;
766 }
767
768 $sortorder = $current->get('sortorder');
769 if ($sortorder >= $max) {
770 return false;
771 }
772 $sortorder = $sortorder + 1;
773 $current->set('sortorder', $sortorder);
774
775 $filters = array('sortorder' => $sortorder);
776 $children = issuer::get_records($filters);
777 foreach ($children as $needtoswap) {
778 $needtoswap->set('sortorder', $sortorder - 1);
779 $needtoswap->update();
780 }
781
782 // OK - all set.
783 $result = $current->update();
784
785 return $result;
786 }
787
eca128bf
DW
788 /**
789 * Disable an identity issuer.
790 *
791 * Requires moodle/site:config capability at the system context.
792 *
99e3c347 793 * @param int $id The id of the identity issuer to disable.
eca128bf
DW
794 * @return boolean
795 */
796 public static function disable_issuer($id) {
797 require_capability('moodle/site:config', context_system::instance());
798 $issuer = new issuer($id);
799
800 $issuer->set('enabled', 0);
801 return $issuer->update();
802 }
803
804
805 /**
806 * Enable an identity issuer.
807 *
808 * Requires moodle/site:config capability at the system context.
809 *
810 * @param int $id The id of the identity issuer to enable.
811 * @return boolean
812 */
813 public static function enable_issuer($id) {
814 require_capability('moodle/site:config', context_system::instance());
815 $issuer = new issuer($id);
816
817 $issuer->set('enabled', 1);
818 return $issuer->update();
819 }
820
f9f243f9
DW
821 /**
822 * Delete an identity issuer.
823 *
824 * Requires moodle/site:config capability at the system context.
825 *
826 * @param int $id The id of the identity issuer to delete.
827 * @return boolean
828 */
60237253
DW
829 public static function delete_issuer($id) {
830 require_capability('moodle/site:config', context_system::instance());
831 $issuer = new issuer($id);
832
833 $systemaccount = self::get_system_account($issuer);
834 if ($systemaccount) {
835 $systemaccount->delete();
836 }
837 $endpoints = self::get_endpoints($issuer);
838 if ($endpoints) {
839 foreach ($endpoints as $endpoint) {
840 $endpoint->delete();
841 }
842 }
843
844 // Will throw exceptions on validation failures.
8445556b
DW
845 return $issuer->delete();
846 }
847
f9f243f9
DW
848 /**
849 * Delete an endpoint.
850 *
851 * Requires moodle/site:config capability at the system context.
852 *
853 * @param int $id The id of the endpoint to delete.
854 * @return boolean
855 */
8445556b
DW
856 public static function delete_endpoint($id) {
857 require_capability('moodle/site:config', context_system::instance());
858 $endpoint = new endpoint($id);
859
860 // Will throw exceptions on validation failures.
861 return $endpoint->delete();
862 }
863
f9f243f9
DW
864 /**
865 * Delete a user_field_mapping.
866 *
867 * Requires moodle/site:config capability at the system context.
868 *
869 * @param int $id The id of the user_field_mapping to delete.
870 * @return boolean
871 */
8445556b
DW
872 public static function delete_user_field_mapping($id) {
873 require_capability('moodle/site:config', context_system::instance());
874 $userfieldmapping = new user_field_mapping($id);
875
876 // Will throw exceptions on validation failures.
877 return $userfieldmapping->delete();
60237253
DW
878 }
879
f9f243f9
DW
880 /**
881 * Perform the OAuth dance and get a refresh token.
882 *
883 * Requires moodle/site:config capability at the system context.
884 *
bd0b9873 885 * @param \core\oauth2\issuer $issuer
f9f243f9
DW
886 * @param moodle_url $returnurl The url to the current page (we will be redirected back here after authentication).
887 * @return boolean
888 */
60237253
DW
889 public static function connect_system_account($issuer, $returnurl) {
890 require_capability('moodle/site:config', context_system::instance());
891
892 // We need to authenticate with an oauth 2 client AS a system user and get a refresh token for offline access.
931c0234 893 $scopes = self::get_system_scopes_for_issuer($issuer);
60237253
DW
894
895 // Allow callbacks to inject non-standard scopes to the auth request.
896
931c0234 897 $client = new client($issuer, $returnurl, $scopes, true);
60237253
DW
898
899 if (!optional_param('response', false, PARAM_BOOL)) {
900 $client->log_out();
901 }
902
8445556b
DW
903 if (optional_param('error', '', PARAM_RAW)) {
904 return false;
905 }
906
60237253
DW
907 if (!$client->is_logged_in()) {
908 redirect($client->get_login_url());
909 }
910
911 $refreshtoken = $client->get_refresh_token();
912 if (!$refreshtoken) {
913 return false;
914 }
915
916 $systemaccount = self::get_system_account($issuer);
917 if ($systemaccount) {
918 $systemaccount->delete();
919 }
28dddbc1
DW
920
921 $userinfo = $client->get_userinfo();
922
60237253
DW
923 $record = new stdClass();
924 $record->issuerid = $issuer->get('id');
925 $record->refreshtoken = $refreshtoken;
29911249 926 $record->grantedscopes = $scopes;
3fa588c6 927 $record->email = isset($userinfo['email']) ? $userinfo['email'] : '';
28dddbc1 928 $record->username = $userinfo['username'];
60237253
DW
929
930 $systemaccount = new system_account(0, $record);
931
932 $systemaccount->create();
933
934 $client->log_out();
935 return true;
936 }
937}