MDL-65547 tool_mobile: New WS for generating tokens with qr login keys

This commit is contained in:
Juan Leyva 2020-04-14 21:17:43 +02:00
parent 9df5151013
commit 118852a710
4 changed files with 235 additions and 2 deletions

View file

@ -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(),
]
);
}
} }

View file

@ -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,
),
);

View file

@ -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);
}
} }

View file

@ -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(