From ee12a8e152fe9cbdf2601643c3f8a39df3489305 Mon Sep 17 00:00:00 2001 From: PMKuipers Date: Mon, 26 Jun 2023 11:41:13 +0200 Subject: [PATCH] Fixed security issues and added enrolcohortsync service --- classes/associationservice.php | 77 +++++++++++++++++++++++++++++++++- db/services.php | 11 ++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/classes/associationservice.php b/classes/associationservice.php index 600668f..e6c66e4 100644 --- a/classes/associationservice.php +++ b/classes/associationservice.php @@ -1,10 +1,15 @@ libdir.'/externallib.php'); class associationservice extends \external_api { + const CAP_EDIT = "local/treestudyplan:editstudyplan"; + const CAP_VIEW = "local/treestudyplan:viewuserreports"; + public static function user_structure(){ return new \external_single_structure([ "id" => new \external_value(PARAM_INT, 'user id'), @@ -76,6 +81,7 @@ class associationservice extends \external_api return new \external_function_parameters( [ 'like' => new \external_value(PARAM_TEXT, 'search text', VALUE_OPTIONAL), 'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL), + 'context_ic' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL), ] ); } @@ -85,9 +91,14 @@ class associationservice extends \external_api } // Actual functions - public static function list_cohort($like='',$exclude_id=null) + public static function list_cohort($like='',$exclude_id=null,$context_id=1) { global $CFG, $DB; + + // Only allow this if the user has the right to edit in this context + $context = webservicehelper::find_context($context_id); + webservicehelper::require_capabilities(self::CAP_EDIT, $context); + $pattern = "%{$like}%"; $params = ["pattern_nm" => $pattern,"pattern_id" => $pattern,]; @@ -98,6 +109,11 @@ class associationservice extends \external_api $sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)"; $params['exclude_id'] = $exclude_id; } + if($context_id > 1){ // system context returns all cohorts, including system cohorts + // otherwise, + $sql .= " AND contextid = :context_id"; + $params['context_id'] = $context_id; + } $cohorts = []; $rs = $DB->get_recordset_sql($sql,$params); @@ -113,6 +129,7 @@ class associationservice extends \external_api return new \external_function_parameters( [ 'like' => new \external_value(PARAM_TEXT, 'search text'), 'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL), + 'context_id' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL), ] ); } @@ -122,10 +139,14 @@ class associationservice extends \external_api } // Actual functions - public static function find_user($like,$exclude_id=null) + public static function find_user($like,$exclude_id=null,$context_id=1) { global $CFG, $DB; + // Only allow this if the user has the right to edit in this context (using system rights would make things more confusing) + $context = webservicehelper::find_context($context_id); + webservicehelper::require_capabilities(self::CAP_EDIT,); + $pattern = "%{$like}%"; $params = ["pattern_fn" => $pattern, "pattern_ln" => $pattern, @@ -169,6 +190,10 @@ class associationservice extends \external_api public static function connect_cohort($studyplan_id,$cohort_id) { global $CFG, $DB; + + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); + if(!$DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) { $id = $DB->insert_record('local_treestudyplan_cohort', [ @@ -205,6 +230,9 @@ class associationservice extends \external_api { global $CFG, $DB; + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); + if($DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) { $DB->delete_records('local_treestudyplan_cohort', [ @@ -239,6 +267,10 @@ class associationservice extends \external_api public static function connect_user($studyplan_id,$user_id) { global $CFG, $DB; + + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); + if(!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) { $id = $DB->insert_record('local_treestudyplan_user', [ @@ -273,6 +305,8 @@ class associationservice extends \external_api public static function disconnect_user($studyplan_id,$user_id) { global $CFG, $DB; + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); if($DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) { @@ -303,6 +337,8 @@ class associationservice extends \external_api public static function associated_users($studyplan_id) { global $CFG, $DB; + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); $sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id"; $sql .= " WHERE j.studyplan_id = :studyplan_id"; @@ -334,6 +370,8 @@ class associationservice extends \external_api public static function associated_cohorts($studyplan_id) { global $CFG, $DB; + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); $sql = "SELECT DISTINCT c.* FROM {cohort} c INNER JOIN {local_treestudyplan_cohort} j ON j.cohort_id = c.id"; $sql .= " WHERE j.studyplan_id = :studyplan_id"; @@ -365,6 +403,10 @@ class associationservice extends \external_api { global $CFG, $DB; + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); + + $users = []; // SQL JOIN script selecting all users that have a cohort linked to this studyplan // or are directly linked @@ -405,4 +447,35 @@ class associationservice extends \external_api return ($cmp != 0)?$cmp:$a['firstname'] <=> $b['firstname']; }); } + + public static function enrol_cohortsync_parameters() + { + return new \external_function_parameters( [ + "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), + ] ); + } + + public static function enrol_cohortsync_returns() + { + return success::structure(); + } + + // Actual functions + public static function enrol_cohortsync($studyplan_id) + { + $studyplan = studyplan::findById($studyplan_id); + webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); + + + $enroller = new enrolcohortsync($studyplan); + $result = $enroller->sync(); + + return $result?success::success():success::fail(); + + } + + + + + } \ No newline at end of file diff --git a/db/services.php b/db/services.php index 3095621..d311335 100644 --- a/db/services.php +++ b/db/services.php @@ -541,5 +541,14 @@ $functions = [ 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'ajax' => true, 'loginrequired' => true, - ], + ], + 'local_treestudyplan_enrol_cohortsync' => [ //web service function name + 'classname' => '\local_treestudyplan\associationservice', //class containing the external function + 'methodname' => 'enrol_cohortsync', //external function name + 'description' => 'Sync cohortsync to studyplan association', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required + 'ajax' => true, + 'loginrequired' => true, + ], ];