mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 01:16:44 +02:00
MDL-65547 tool_mobile: New WS for generating tokens with qr login keys
This commit is contained in:
parent
9df5151013
commit
118852a710
4 changed files with 235 additions and 2 deletions
|
@ -39,6 +39,7 @@ use context_system;
|
||||||
use moodle_exception;
|
use moodle_exception;
|
||||||
use moodle_url;
|
use moodle_url;
|
||||||
use core_text;
|
use core_text;
|
||||||
|
use core_user;
|
||||||
use coding_exception;
|
use coding_exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -593,4 +594,102 @@ class external extends external_api {
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns description of get_tokens_for_qr_login() parameters.
|
||||||
|
*
|
||||||
|
* @return external_function_parameters
|
||||||
|
* @since Moodle 3.9
|
||||||
|
*/
|
||||||
|
public static function get_tokens_for_qr_login_parameters() {
|
||||||
|
return new external_function_parameters (
|
||||||
|
[
|
||||||
|
'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
|
||||||
|
'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a WebService token (and private token) for QR login
|
||||||
|
*
|
||||||
|
* @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
|
||||||
|
* @param int $userid the user the key belongs to
|
||||||
|
* @return array with the tokens and warnings
|
||||||
|
* @since Moodle 3.9
|
||||||
|
*/
|
||||||
|
public static function get_tokens_for_qr_login($qrloginkey, $userid) {
|
||||||
|
global $PAGE, $DB;
|
||||||
|
|
||||||
|
$params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
|
||||||
|
['qrloginkey' => $qrloginkey, 'userid' => $userid]);
|
||||||
|
|
||||||
|
$context = context_system::instance();
|
||||||
|
// We need this to make work the format text functions.
|
||||||
|
$PAGE->set_context($context);
|
||||||
|
|
||||||
|
$enableqrlogin = get_config('tool_mobile', 'enableqrlogin');
|
||||||
|
if (empty($enableqrlogin)) {
|
||||||
|
throw new moodle_exception('QR login not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
|
||||||
|
// This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
|
||||||
|
// is used by other PHP scripts that can be opened in any browser.
|
||||||
|
if (!\core_useragent::is_moodle_app()) {
|
||||||
|
throw new moodle_exception('apprequired', 'tool_mobile');
|
||||||
|
}
|
||||||
|
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
|
||||||
|
|
||||||
|
// Validate and delete the key.
|
||||||
|
$key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
|
||||||
|
delete_user_key('tool_mobile', $params['userid']);
|
||||||
|
|
||||||
|
// Double check key belong to user.
|
||||||
|
if ($key->userid != $params['userid']) {
|
||||||
|
throw new moodle_exception('invalidkey');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key validated, check user.
|
||||||
|
$user = core_user::get_user($key->userid, '*', MUST_EXIST);
|
||||||
|
core_user::require_active_user($user, true, true);
|
||||||
|
|
||||||
|
// Generate WS tokens.
|
||||||
|
\core\session\manager::set_user($user);
|
||||||
|
|
||||||
|
// Check if the service exists and is enabled.
|
||||||
|
$service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
|
||||||
|
if (empty($service)) {
|
||||||
|
// will throw exception if no token found
|
||||||
|
throw new moodle_exception('servicenotavailable', 'webservice');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an existing token or create a new one.
|
||||||
|
$token = external_generate_token_for_current_user($service);
|
||||||
|
$privatetoken = $token->privatetoken; // Save it here, the next function removes it.
|
||||||
|
external_log_token_request($token);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'token' => $token->token,
|
||||||
|
'privatetoken' => $privatetoken ?: '',
|
||||||
|
'warnings' => [],
|
||||||
|
];
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns description of get_tokens_for_qr_login() result value.
|
||||||
|
*
|
||||||
|
* @return external_description
|
||||||
|
* @since Moodle 3.9
|
||||||
|
*/
|
||||||
|
public static function get_tokens_for_qr_login_returns() {
|
||||||
|
return new external_single_structure(
|
||||||
|
[
|
||||||
|
'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
|
||||||
|
'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
|
||||||
|
'warnings' => new external_warnings(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,5 +78,14 @@ $functions = array(
|
||||||
'type' => 'write',
|
'type' => 'write',
|
||||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
|
|
||||||
|
'tool_mobile_get_tokens_for_qr_login' => array(
|
||||||
|
'classname' => 'tool_mobile\external',
|
||||||
|
'methodname' => 'get_tokens_for_qr_login',
|
||||||
|
'description' => 'Returns a WebService token (and private token) for QR login.',
|
||||||
|
'type' => 'read',
|
||||||
|
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||||
|
'ajax' => true,
|
||||||
|
'loginrequired' => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
@ -600,4 +600,129 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||||
$expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]);
|
$expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]);
|
||||||
$this->assertEquals($expected, $data->courses[0]->summary);
|
$this->assertEquals($expected, $data->courses[0]->summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test get_tokens_for_qr_login.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login() {
|
||||||
|
global $DB, $CFG, $USER;
|
||||||
|
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$user = $this->getDataGenerator()->create_user();
|
||||||
|
$this->setUser($user);
|
||||||
|
|
||||||
|
$qrloginkey = api::get_qrlogin_key();
|
||||||
|
|
||||||
|
// Generate new tokens, the ones we expect to receive.
|
||||||
|
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
|
||||||
|
$token = external_generate_token_for_current_user($service);
|
||||||
|
|
||||||
|
// Fake the app.
|
||||||
|
core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
|
||||||
|
|
||||||
|
$result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
|
||||||
|
$result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result);
|
||||||
|
|
||||||
|
$this->assertEmpty($result['warnings']);
|
||||||
|
$this->assertEquals($token->token, $result['token']);
|
||||||
|
$this->assertEquals($token->privatetoken, $result['privatetoken']);
|
||||||
|
|
||||||
|
// Now, try with an invalid key.
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$this->expectExceptionMessage(get_string('invalidkey', 'error'));
|
||||||
|
$result = external::get_tokens_for_qr_login(random_string('64'), $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_tokens_for_qr_login missing QR code enabled.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login_missing_enableqr() {
|
||||||
|
global $CFG, $USER;
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
$this->setAdminUser();
|
||||||
|
|
||||||
|
set_config('enableqrlogin', 1, 'tool_mobile');
|
||||||
|
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$result = external::get_tokens_for_qr_login('', $USER->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_tokens_for_qr_login missing ws.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login_missing_ws() {
|
||||||
|
global $CFG;
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$user = $this->getDataGenerator()->create_user();
|
||||||
|
$this->setUser($user);
|
||||||
|
|
||||||
|
// Fake the app.
|
||||||
|
core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
|
||||||
|
|
||||||
|
// Need to disable webservices to verify that's checked.
|
||||||
|
$CFG->enablewebservices = 0;
|
||||||
|
$CFG->enablemobilewebservice = 0;
|
||||||
|
|
||||||
|
$this->setAdminUser();
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
|
||||||
|
$result = external::get_tokens_for_qr_login('', $user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_tokens_for_qr_login missing https.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login_missing_https() {
|
||||||
|
global $CFG, $USER;
|
||||||
|
|
||||||
|
// Fake the app.
|
||||||
|
core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
|
||||||
|
|
||||||
|
// Need to simulate a non HTTPS site here.
|
||||||
|
$CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
|
||||||
|
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
$this->setAdminUser();
|
||||||
|
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
|
||||||
|
$result = external::get_tokens_for_qr_login('', $USER->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_tokens_for_qr_login missing admin.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login_missing_admin() {
|
||||||
|
global $CFG, $USER;
|
||||||
|
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
$this->setAdminUser();
|
||||||
|
|
||||||
|
// Fake the app.
|
||||||
|
core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
|
||||||
|
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
|
||||||
|
$result = external::get_tokens_for_qr_login('', $USER->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_tokens_for_qr_login missing app_request.
|
||||||
|
*/
|
||||||
|
public function test_get_tokens_for_qr_login_missing_app_request() {
|
||||||
|
global $CFG, $USER;
|
||||||
|
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
$this->setAdminUser();
|
||||||
|
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
|
||||||
|
$result = external::get_tokens_for_qr_login('', $USER->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
$plugin->version = 2019111800; // The current plugin version (Date: YYYYMMDDXX).
|
$plugin->version = 2019111801; // The current plugin version (Date: YYYYMMDDXX).
|
||||||
$plugin->requires = 2019111200; // Requires this Moodle version.
|
$plugin->requires = 2019111200; // Requires this Moodle version.
|
||||||
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
|
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
|
||||||
$plugin->dependencies = array(
|
$plugin->dependencies = array(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue