MDL-58090 oauth2: Store a list of oauth2 services
[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 }
79 }
80
81 /**
82 * We don't want to allow users setting an internal password.
83 *
84 * @return bool
85 */
86 public function prevent_local_passwords() {
87 return true;
88 }
89
90 /**
91 * Returns true if this authentication plugin is 'internal'.
92 *
93 * @return bool
94 */
95 public function is_internal() {
96 return false;
97 }
98
99 /**
100 * Indicates if moodle should automatically update internal user
101 * records with data from external sources using the information
102 * from auth_plugin_base::get_userinfo().
103 *
104 * @return bool true means automatically copy data from ext to user table
105 */
106 public function is_synchronised_with_external() {
107 return true;
108 }
109
110 /**
111 * Returns true if this authentication plugin can change the user's
112 * password.
113 *
114 * @return bool
115 */
116 public function can_change_password() {
117 return false;
118 }
119
120 /**
121 * Returns the URL for changing the user's pw, or empty if the default can
122 * be used.
123 *
124 * @return moodle_url
125 */
126 public function change_password_url() {
127 return null;
128 }
129
130 /**
131 * Returns true if plugin allows resetting of internal password.
132 *
133 * @return bool
134 */
135 public function can_reset_password() {
136 return false;
137 }
138
139 /**
140 * Returns true if plugin can be manually set.
141 *
142 * @return bool
143 */
144 public function can_be_manually_set() {
145 return true;
146 }
147
148 /**
149 * Prints a form for configuring this authentication plugin.
150 *
151 * This function is called from admin/auth.php, and outputs a full page with
152 * a form for configuring this plugin.
153 *
154 * @param stdClass $config
155 * @param string $err
156 * @param array userfields
157 */
158 public function config_form($config, $err, $userfields) {
159 echo get_string('plugindescription', 'auth_oauth2');
160
161 // Force all fields updated on login and locked.
162
163 foreach ($userfields as $field) {
164 set_config('field_updatelocal_' . $field, 'onlogin', 'auth_oauth2');
165 set_config('field_lock_' . $field, 'unlockedifempty', 'auth_oauth2');
166 }
167 return;
168 }
169
170 /**
171 * Return the userinfo from the oauth handshake. Will only be valid
172 * for the logged in user.
173 */
174 public function get_userinfo($username) {
175 $cached = $this->get_static_user_info();
176 if (!empty($cached) && $cached['username'] == $username) {
177 return $cached;
178 }
179 return false;
180 }
181
182 private function is_ready_for_login_page($issuer) {
183 return !empty($issuer->get('clientid')) &&
184 !empty($issuer->get('clientsecret')) &&
185 $issuer->is_authentication_supported() &&
186 !empty($issuer->get('showonloginpage'));
187 }
188
189 /**
190 * Return a list of identity providers to display on the login page.
191 */
192 public function loginpage_idp_list($wantsurl) {
193 $providers = \core\oauth2\api::get_all_issuers();
194 $result = [];
195 if (empty($wantsurl)) {
196 $wantsurl = '/';
197 }
198 foreach ($providers as $idp) {
199 if ($this->is_ready_for_login_page($idp)) {
200 $params = ['id' => $idp->get('id'), 'wantsurl' => $wantsurl, 'sesskey' => sesskey()];
201 $url = new moodle_url('/auth/oauth2/login.php', $params);
202 $icon = $idp->get('image');
203 $result[] = ['url' => $url, 'iconurl' => $icon, 'name' => $idp->get('name')];
204 }
205 }
206 return $result;
207 }
208
209 /**
210 * Statically cache the user info from the oauth handshake
211 * @param stdClass $userinfo
212 */
213 private function set_static_user_info($userinfo) {
214 self::$userinfo = $userinfo;
215 }
216
217 /**
218 * Get the static cached user info
219 * @return stdClass
220 */
221 private function get_static_user_info() {
222 return self::$userinfo;
223 }
224
225 /**
226 * Statically cache the user picture from the oauth handshake
227 * @param string $userpicture
228 */
229 private function set_static_user_picture($userpicture) {
230 self::$userpicture = $userpicture;
231 }
232
233 /**
234 * Get the static cached user picture
235 * @return string
236 */
237 private function get_static_user_picture() {
238 return self::$userpicture;
239 }
240
241 /**
242 * If this user has no picture - but we got one from oauth - set it.
243 * @return boolean True if the image was updated.
244 */
245 private function update_picture($user) {
246 global $CFG, $DB, $USER;
247
248 require_once($CFG->libdir . '/filelib.php');
249 require_once($CFG->libdir . '/gdlib.php');
250
251 $fs = get_file_storage();
252 $userid = $user->id;
253 if (!empty($user->picture)) {
254 return false;
255 }
256 $picture = $this->get_static_user_picture();
257 if (empty($picture)) {
258 return false;
259 }
260
261 $context = \context_user::instance($userid, MUST_EXIST);
262 $fs->delete_area_files($context->id, 'user', 'newicon');
263
264 $filerecord = array(
265 'contextid' => $context->id,
266 'component' => 'user',
267 'filearea' => 'newicon',
268 'itemid' => 0,
269 'filepath' => '/',
270 'filename' => 'image'
271 );
272
273 try {
274 $fs->create_file_from_string($filerecord, $picture);
275 } catch (\file_exception $e) {
276 return get_string($e->errorcode, $e->module, $e->a);
277 }
278
279 $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
280
281 // There should only be one.
282 $iconfile = reset($iconfile);
283
284 // Something went wrong while creating temp file - remove the uploaded file.
285 if (!$iconfile = $iconfile->copy_content_to_temp()) {
286 $fs->delete_area_files($context->id, 'user', 'newicon');
287 return false;
288 }
289
290 // Copy file to temporary location and the send it for processing icon.
291 $newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
292 // Delete temporary file.
293 @unlink($iconfile);
294 // Remove uploaded file.
295 $fs->delete_area_files($context->id, 'user', 'newicon');
296 // Set the user's picture.
297 $updateuser = new stdClass();
298 $updateuser->id = $userid;
299 $updateuser->picture = $newpicture;
300 $USER->picture = $newpicture;
301 user_update_user($updateuser);
302 return true;
303 }
304
305 /**
306 * Complete the login process after oauth handshake is complete.
307 * @param \core\oauth2\client $client
308 * @param string $redirecturl
309 * @return none Either redirects or throws an exception
310 */
311 public function complete_login(client $client, $redirecturl) {
312 global $CFG, $SESSION;
313
314 $userinfo = $client->get_userinfo();
315
316 if (!$userinfo) {
317 $errormsg = get_string('notloggedin', 'auth_oauth2');
318 $SESSION->loginerrormsg = $errormsg;
319 redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
320 }
321
322 $userinfo['username'] = trim(core_text::strtolower($userinfo['username']));
323
324 if (!empty($userinfo['picture'])) {
325 $this->set_static_user_picture($userinfo['picture']);
326 unset($userinfo['picture']);
327 }
328
329 if (!empty($userinfo['lang'])) {
330 $userinfo['lang'] = str_replace('-', '_', trim(core_text::strtolower($userinfo['lang'])));
331 if (!get_string_manager()->translation_exists($userinfo['lang'], false)) {
332 unset($userinfo['lang']);
333 }
334 }
335 $this->set_static_user_info($userinfo);
336
337 $user = authenticate_user_login($userinfo['username'], '');
338
339 if ($user) {
340 complete_user_login($user);
341 $this->update_picture($user);
342 redirect($redirecturl);
343 }
344 $errormsg = get_string('notloggedin', 'auth_oauth2');
345 $SESSION->loginerrormsg = $errormsg;
346 redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
347 }
348}
349
350