MDL-58334 repositories: Offline downloads
[moodle.git] / auth / oauth2 / classes / auth.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 * Anobody can login with any password.
19 *
20 * @package auth_oauth2
21 * @copyright 2017 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
23 */
24
25namespace auth_oauth2;
26
27defined('MOODLE_INTERNAL') || die();
28
29use pix_icon;
30use moodle_url;
31use core_text;
32use stdClass;
33use core\oauth2\issuer;
34use core\oauth2\client;
35
36require_once($CFG->libdir.'/authlib.php');
37
38/**
39 * Plugin for oauth2 authentication.
40 *
41 * @package auth_oauth2
42 * @copyright 2017 Damyon Wiese
43 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
44 */
45class auth extends \auth_plugin_base {
46
47 /**
48 * @var stdClass $userinfo The set of user info returned from the oauth handshake
49 */
50 private static $userinfo;
51
52 /**
53 * @var stdClass $userpicture The url to a picture.
54 */
55 private static $userpicture;
56
57 /**
58 * Constructor.
59 */
60 public function __construct() {
61 $this->authtype = 'oauth2';
62 $this->config = get_config('auth_oauth2');
63 }
64
65 /**
66 * Returns true if the username and password work or don't exist and false
67 * if the user exists and the password is wrong.
68 *
69 * @param string $username The username
70 * @param string $password The password
71 * @return bool Authentication success or failure.
72 */
73 public function user_login($username, $password) {
74 $cached = $this->get_static_user_info();
75 $verifyusername = $cached['username'];
76 if ($verifyusername == $username) {
77 return true;
78 }
979d1f66 79 return false;
60237253
DW
80 }
81
82 /**
83 * We don't want to allow users setting an internal password.
84 *
85 * @return bool
86 */
87 public function prevent_local_passwords() {
88 return true;
89 }
90
91 /**
92 * Returns true if this authentication plugin is 'internal'.
93 *
94 * @return bool
95 */
96 public function is_internal() {
97 return false;
98 }
99
100 /**
101 * Indicates if moodle should automatically update internal user
102 * records with data from external sources using the information
103 * from auth_plugin_base::get_userinfo().
104 *
105 * @return bool true means automatically copy data from ext to user table
106 */
107 public function is_synchronised_with_external() {
108 return true;
109 }
110
111 /**
112 * Returns true if this authentication plugin can change the user's
113 * password.
114 *
115 * @return bool
116 */
117 public function can_change_password() {
118 return false;
119 }
120
121 /**
122 * Returns the URL for changing the user's pw, or empty if the default can
123 * be used.
124 *
125 * @return moodle_url
126 */
127 public function change_password_url() {
128 return null;
129 }
130
131 /**
132 * Returns true if plugin allows resetting of internal password.
133 *
134 * @return bool
135 */
136 public function can_reset_password() {
137 return false;
138 }
139
140 /**
141 * Returns true if plugin can be manually set.
142 *
143 * @return bool
144 */
145 public function can_be_manually_set() {
146 return true;
147 }
148
149 /**
150 * Prints a form for configuring this authentication plugin.
151 *
152 * This function is called from admin/auth.php, and outputs a full page with
153 * a form for configuring this plugin.
154 *
155 * @param stdClass $config
156 * @param string $err
29911249 157 * @param array $userfields
60237253
DW
158 */
159 public function config_form($config, $err, $userfields) {
1dca8d1a 160 include(__DIR__ . "/../config.html");
60237253 161
60237253
DW
162 return;
163 }
164
165 /**
166 * Return the userinfo from the oauth handshake. Will only be valid
167 * for the logged in user.
72fd103a 168 * @param string $username
60237253
DW
169 */
170 public function get_userinfo($username) {
171 $cached = $this->get_static_user_info();
172 if (!empty($cached) && $cached['username'] == $username) {
173 return $cached;
174 }
175 return false;
176 }
177
f9f243f9
DW
178 /**
179 * Do some checks on the identity provider before showing it on the login page.
29911249 180 * @param core\oauth2\issuer $issuer
f9f243f9
DW
181 * @return boolean
182 */
183 private function is_ready_for_login_page(\core\oauth2\issuer $issuer) {
eca128bf
DW
184 return $issuer->get('enabled') &&
185 !empty($issuer->get('clientid')) &&
60237253
DW
186 !empty($issuer->get('clientsecret')) &&
187 $issuer->is_authentication_supported() &&
188 !empty($issuer->get('showonloginpage'));
189 }
190
191 /**
192 * Return a list of identity providers to display on the login page.
f9f243f9
DW
193 *
194 * @param string|moodle_url $wantsurl The requested URL.
195 * @return array (containing url, iconurl and name).
60237253
DW
196 */
197 public function loginpage_idp_list($wantsurl) {
198 $providers = \core\oauth2\api::get_all_issuers();
199 $result = [];
200 if (empty($wantsurl)) {
201 $wantsurl = '/';
202 }
203 foreach ($providers as $idp) {
204 if ($this->is_ready_for_login_page($idp)) {
205 $params = ['id' => $idp->get('id'), 'wantsurl' => $wantsurl, 'sesskey' => sesskey()];
206 $url = new moodle_url('/auth/oauth2/login.php', $params);
207 $icon = $idp->get('image');
208 $result[] = ['url' => $url, 'iconurl' => $icon, 'name' => $idp->get('name')];
209 }
210 }
211 return $result;
212 }
213
214 /**
215 * Statically cache the user info from the oauth handshake
216 * @param stdClass $userinfo
217 */
218 private function set_static_user_info($userinfo) {
219 self::$userinfo = $userinfo;
220 }
221
222 /**
223 * Get the static cached user info
224 * @return stdClass
225 */
226 private function get_static_user_info() {
227 return self::$userinfo;
228 }
229
230 /**
231 * Statically cache the user picture from the oauth handshake
232 * @param string $userpicture
233 */
234 private function set_static_user_picture($userpicture) {
235 self::$userpicture = $userpicture;
236 }
237
238 /**
239 * Get the static cached user picture
240 * @return string
241 */
242 private function get_static_user_picture() {
243 return self::$userpicture;
244 }
245
246 /**
247 * If this user has no picture - but we got one from oauth - set it.
29911249 248 * @param stdClass $user
60237253
DW
249 * @return boolean True if the image was updated.
250 */
251 private function update_picture($user) {
252 global $CFG, $DB, $USER;
253
254 require_once($CFG->libdir . '/filelib.php');
255 require_once($CFG->libdir . '/gdlib.php');
256
257 $fs = get_file_storage();
258 $userid = $user->id;
259 if (!empty($user->picture)) {
260 return false;
261 }
262 $picture = $this->get_static_user_picture();
263 if (empty($picture)) {
264 return false;
265 }
266
267 $context = \context_user::instance($userid, MUST_EXIST);
268 $fs->delete_area_files($context->id, 'user', 'newicon');
269
270 $filerecord = array(
271 'contextid' => $context->id,
272 'component' => 'user',
273 'filearea' => 'newicon',
274 'itemid' => 0,
275 'filepath' => '/',
276 'filename' => 'image'
277 );
278
279 try {
280 $fs->create_file_from_string($filerecord, $picture);
281 } catch (\file_exception $e) {
282 return get_string($e->errorcode, $e->module, $e->a);
283 }
284
285 $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
286
287 // There should only be one.
288 $iconfile = reset($iconfile);
289
290 // Something went wrong while creating temp file - remove the uploaded file.
291 if (!$iconfile = $iconfile->copy_content_to_temp()) {
292 $fs->delete_area_files($context->id, 'user', 'newicon');
293 return false;
294 }
295
296 // Copy file to temporary location and the send it for processing icon.
297 $newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
298 // Delete temporary file.
299 @unlink($iconfile);
300 // Remove uploaded file.
301 $fs->delete_area_files($context->id, 'user', 'newicon');
302 // Set the user's picture.
303 $updateuser = new stdClass();
304 $updateuser->id = $userid;
305 $updateuser->picture = $newpicture;
306 $USER->picture = $newpicture;
307 user_update_user($updateuser);
308 return true;
309 }
310
72fd103a
DW
311 /**
312 * Process the config after the form is saved.
313 * @param stdClass $config
314 */
1dca8d1a 315 public function process_config($config) {
72fd103a 316 // Set to defaults if undefined.
1dca8d1a
DW
317 if (!isset($config->allowlinkedlogins)) {
318 $config->allowlinkedlogins = false;
319 }
320 set_config('allowlinkedlogins', trim($config->allowlinkedlogins), 'auth_oauth2');
321 }
322
60237253
DW
323 /**
324 * Complete the login process after oauth handshake is complete.
325 * @param \core\oauth2\client $client
326 * @param string $redirecturl
327 * @return none Either redirects or throws an exception
328 */
329 public function complete_login(client $client, $redirecturl) {
330 global $CFG, $SESSION;
331
332 $userinfo = $client->get_userinfo();
333
334 if (!$userinfo) {
335 $errormsg = get_string('notloggedin', 'auth_oauth2');
336 $SESSION->loginerrormsg = $errormsg;
337 redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
8445556b
DW
338 }
339 if (empty($userinfo['username'])) {
340 $errormsg = get_string('notloggedin', 'auth_oauth2');
341 $SESSION->loginerrormsg = $errormsg;
342 redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
60237253
DW
343 }
344
345 $userinfo['username'] = trim(core_text::strtolower($userinfo['username']));
346
1dca8d1a
DW
347 $userwasmapped = false;
348 if (get_config('auth_oauth2', 'allowlinkedlogins')) {
349 $mappeduser = api::match_username_to_user($userinfo['username'], $client->get_issuer());
350
351 if ($mappeduser) {
352 $userinfo = (array) $mappeduser;
353 $userwasmapped = true;
354 }
60237253
DW
355 }
356
1dca8d1a
DW
357 if (!$userwasmapped) {
358 if (!empty($userinfo['picture'])) {
359 $this->set_static_user_picture($userinfo['picture']);
360 unset($userinfo['picture']);
361 }
362
363 if (!empty($userinfo['lang'])) {
364 $userinfo['lang'] = str_replace('-', '_', trim(core_text::strtolower($userinfo['lang'])));
365 if (!get_string_manager()->translation_exists($userinfo['lang'], false)) {
366 unset($userinfo['lang']);
367 }
60237253
DW
368 }
369 }
1dca8d1a 370
c21a66e4 371 $issuer = $client->get_issuer();
60237253 372
c21a66e4
DW
373 $user = false;
374 if ($issuer->is_valid_login_domain($userinfo['email'])) {
375
376 $this->set_static_user_info($userinfo);
377 $user = authenticate_user_login($userinfo['username'], '');
378 }
60237253
DW
379
380 if ($user) {
381 complete_user_login($user);
382 $this->update_picture($user);
383 redirect($redirecturl);
384 }
385 $errormsg = get_string('notloggedin', 'auth_oauth2');
386 $SESSION->loginerrormsg = $errormsg;
387 redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
388 }
389}
390
391