From 1aa648116db3208bda4dd56b7fe93250035fb433 Mon Sep 17 00:00:00 2001 From: Jerome Mouneyrac Date: Thu, 8 Dec 2011 15:36:47 +0800 Subject: [PATCH] MDL-30542 backport recent 2.2 changes about security checks --- lang/en/webservice.php | 2 + webservice/lib.php | 116 +++++++++++++++++++++++++++++++++++++++++ webservice/upload.php | 73 +++++++++++--------------- 3 files changed, 148 insertions(+), 43 deletions(-) diff --git a/lang/en/webservice.php b/lang/en/webservice.php index c0d24884bdb..714f20a8817 100644 --- a/lang/en/webservice.php +++ b/lang/en/webservice.php @@ -96,6 +96,7 @@ $string['externalservicefunctions'] = 'External service functions'; $string['externalservices'] = 'External services'; $string['externalserviceusers'] = 'External service users'; $string['failedtolog'] = 'Failed to log'; +$string['filenameexist'] = 'File name already exists: {$a}'; $string['function'] = 'Function'; $string['functions'] = 'Functions'; $string['generalstructure'] = 'General structure'; @@ -166,6 +167,7 @@ $string['selectspecificuserdescription'] = 'Add the web services user as an auth $string['service'] = 'Service'; $string['servicehelpexplanation'] = 'A service is a set of functions. A service can be accessed by all users or just specified users.'; $string['servicename'] = 'Service name'; +$string['servicenotavailable'] = 'Web service is not available (it is not existing or might be set to disable)'; $string['servicesbuiltin'] = 'Built-in services'; $string['servicescustom'] = 'Custom services'; $string['serviceusers'] = 'Authorised users'; diff --git a/webservice/lib.php b/webservice/lib.php index 8ea5f98711b..fe5ec3dd3dd 100644 --- a/webservice/lib.php +++ b/webservice/lib.php @@ -34,6 +34,122 @@ define('WEBSERVICE_AUTHMETHOD_SESSION_TOKEN', 2); */ class webservice { + /** + * Authenticate user (used by download/upload file scripts) + * @param string $token + * @return array - contains the authenticated user, token and service objects + */ + public function authenticate_user($token) { + global $DB, $CFG; + + // web service must be enabled to use this script + if (!$CFG->enablewebservices) { + throw new webservice_access_exception(get_string('enablewsdescription', 'webservice')); + } + + // Obtain token record + if (!$token = $DB->get_record('external_tokens', array('token' => $token))) { + throw new webservice_access_exception(get_string('invalidtoken', 'webservice')); + } + + // Validate token date + if ($token->validuntil and $token->validuntil < time()) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '', get_string('invalidtimedtoken', 'webservice'), 0); + $DB->delete_records('external_tokens', array('token' => $token->token)); + throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); + } + + // Check ip + if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '', get_string('failedtolog', 'webservice') . ": " . getremoteaddr(), 0); + throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); + } + + //retrieve the user info from the token + $user = $DB->get_record('user', array('id' => $token->userid, 'deleted' => 0), '*', MUST_EXIST); + + // setup user session to check capability + session_set_user($user); + + //assumes that if sid is set then there must be a valid associated session no matter the token type + if ($token->sid) { + $session = session_get_instance(); + if (!$session->session_exists($token->sid)) { + $DB->delete_records('external_tokens', array('sid' => $token->sid)); + throw new webservice_access_exception(get_string('invalidtokensession', 'webservice')); + } + } + + //Refuse non-admin authentication in maintenance mode + $hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); + if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { + throw new webservice_access_exception(get_string('sitemaintenance', 'admin')); + } + + //retrieve web service record + $service = $DB->get_record('external_services', array('id' => $token->externalserviceid, 'enabled' => 1)); + if (empty($service)) { + // will throw exception if no token found + throw new webservice_access_exception(get_string('servicenotavailable', 'webservice')); + } + + //check if there is any required system capability + if ($service->requiredcapability and !has_capability($service->requiredcapability, get_context_instance(CONTEXT_SYSTEM), $user)) { + throw new webservice_access_exception(get_string('missingrequiredcapability', 'webservice', $service->requiredcapability)); + } + + //specific checks related to user restricted service + if ($service->restrictedusers) { + $authoriseduser = $DB->get_record('external_services_users', array('externalserviceid' => $service->id, 'userid' => $user->id)); + + if (empty($authoriseduser)) { + throw new webservice_access_exception(get_string('usernotallowed', 'webservice', $service->name)); + } + + if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) { + throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); + } + + if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) { + throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); + } + } + + //only confirmed user should be able to call web service + if (empty($user->confirmed)) { + add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); + throw new webservice_access_exception(get_string('usernotconfirmed', 'moodle', $user->username)); + } + + //check the user is suspended + if (!empty($user->suspended)) { + add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); + throw new webservice_access_exception(get_string('usersuspended', 'webservice')); + } + + //check if the auth method is nologin (in this case refuse connection) + if ($user->auth == 'nologin') { + add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); + throw new webservice_access_exception(get_string('nologinauth', 'webservice')); + } + + //Check if the user password is expired + $auth = get_auth_plugin($user->auth); + if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { + $days2expire = $auth->password_expire($user->username); + if (intval($days2expire) < 0) { + add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); + throw new webservice_access_exception(get_string('passwordisexpired', 'webservice')); + } + } + + // log token access + $DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id)); + + return array('user' => $user, 'token' => $token, 'service' => $service); + } + + /** * Add a user to the list of authorised user of a given service * @param object $user diff --git a/webservice/upload.php b/webservice/upload.php index 9886b64947e..cf45d552695 100644 --- a/webservice/upload.php +++ b/webservice/upload.php @@ -15,7 +15,14 @@ // along with Moodle. If not, see . /** - * Accpet uploading files by web service token + * Accept uploading files by web service token + * + * POST params: + * token => the web service user token (needed for authentication) + * filepath => the private file aera path (where files will be stored) + * [_FILES] => for example you can send the files with , + * or with curl magic: 'file_1' => '@/path/to/file', or ... + * * @package moodlecore * @subpackage files * @copyright 2011 Dongsheng Cai @@ -25,48 +32,17 @@ define('AJAX_SCRIPT', true); define('NO_MOODLE_COOKIES', true); require_once(dirname(dirname(__FILE__)) . '/config.php'); -$token = required_param('token', PARAM_ALPHANUM); +require_once($CFG->dirroot . '/webservice/lib.php'); $filepath = optional_param('filepath', '/', PARAM_PATH); echo $OUTPUT->header(); -// web service must be enabled to use this script -if (!$CFG->enablewebservices) { - throw new moodle_exception('enablewsdescription', 'webservice'); -} -// Obtain token record -if (!$token = $DB->get_record('external_tokens', array('token'=>$token))) { - throw new webservice_access_exception(get_string('invalidtoken', 'webservice')); -} +//authenticate the user +$token = required_param('token', PARAM_ALPHANUM); +$webservicelib = new webservice(); +$authenticationinfo = $webservicelib->authenticate_user($token); -// Validate token date -if ($token->validuntil and $token->validuntil < time()) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('invalidtimedtoken', 'webservice'), 0); - $DB->delete_records('external_tokens', array('token'=>$token->token)); - throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); -} - -//assumes that if sid is set then there must be a valid associated session no matter the token type -if ($token->sid) { - $session = session_get_instance(); - if (!$session->session_exists($token->sid)) { - $DB->delete_records('external_tokens', array('sid'=>$token->sid)); - throw new webservice_access_exception(get_string('invalidtokensession', 'webservice')); - } -} - -// Check ip -if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr(), 0); - throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); -} - -$user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST); - -// log token access -$DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id)); - -session_set_user($user); +//check the user can manage his own files (can upload) $context = get_context_instance(CONTEXT_USER, $USER->id); require_capability('moodle/user:manageownfiles', $context); @@ -106,9 +82,10 @@ foreach ($_FILES as $fieldname=>$uploaded_file) { $file = new stdClass(); $file->filename = clean_param($_FILES[$fieldname]['name'], PARAM_FILE); // check system maxbytes setting - if (($_FILES[$fieldname]['size'] > $CFG->maxbytes)) { + if (($_FILES[$fieldname]['size'] > get_max_upload_file_size($CFG->maxbytes))) { // oversize file will be ignored, error added to array to notify // web service client + $file->errortype = 'fileoversized'; $file->error = get_string('maxbytes', 'error'); } else { $file->filepath = $_FILES[$fieldname]['tmp_name']; @@ -146,9 +123,19 @@ foreach ($files as $file) { $file_record->filepath = $filepath; $file_record->itemid = 0; $file_record->license = $CFG->sitedefaultlicense; - $file_record->author = fullname($user);; + $file_record->author = fullname($authenticationinfo['user']); $file_record->source = ''; - $stored_file = $fs->create_file_from_pathname($file_record, $file->filepath); - $results[] = $file_record; + + //Check if the file already exist + $existingfile = $fs->file_exists($file_record->contextid, $file_record->component, $file_record->filearea, + $file_record->itemid, $file_record->filepath, $file_record->filename); + if ($existingfile) { + $file->errortype = 'filenameexist'; + $file->error = get_string('filenameexist', 'webservice', $file->filename); + $results[] = $file; + } else { + $stored_file = $fs->create_file_from_pathname($file_record, $file->filepath); + $results[] = $file_record; + } } -echo json_encode($results); +echo json_encode($results); \ No newline at end of file