diff --git a/.github/workflows/autograde-service-tests.yml b/.github/workflows/autograde-service-tests.yml index 5c56afdaca3822124589cb4e2e04d63166164e27..b861b07ea8a0bce7e71effca0b72295efe504bce 100644 --- a/.github/workflows/autograde-service-tests.yml +++ b/.github/workflows/autograde-service-tests.yml @@ -2,9 +2,6 @@ name: Spring App tests on: push: - paths: - - "moodle-grading-service/**" - - ".github/workflows/autograde-service-tests.yml" jobs: build: @@ -23,4 +20,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: test-report - path: moodle-grading-service/target/surefire-reports \ No newline at end of file + path: moodle-grading-service/target/surefire-reports diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000000000000000000000000000000000000..039b9aa01e94241da95f10896cf57f7ef2565d3e --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="DataSourceManagerImpl" format="xml" multifile-model="true"> + <data-source source="LOCAL" name="moodle-dev" uuid="55f0af50-0db1-4bef-8422-991545d60c56"> + <driver-ref>mysql.8</driver-ref> + <synchronize>true</synchronize> + <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver> + <jdbc-url>jdbc:mysql://localhost:3305/moodle</jdbc-url> + <working-dir>$ProjectFileDir$</working-dir> + </data-source> + </component> +</project> \ No newline at end of file diff --git a/.idea/moodle-autograde.iml b/.idea/moodle-autograde.iml index d6ebd4805981b8400db3e3291c74a743fef9a824..1fb44a343824b2b3a9b726c39b9eb072a41383de 100644 --- a/.idea/moodle-autograde.iml +++ b/.idea/moodle-autograde.iml @@ -2,7 +2,83 @@ <module type="JAVA_MODULE" version="4"> <component name="NewModuleRootManager" inherit-compiler-output="true"> <exclude-output /> - <content url="file://$MODULE_DIR$" /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/moodle/admin/presets/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/admin/roles/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/admin/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/analytics/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/auth/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/availability/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/backup/controller/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/backup/converter/moodle1/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/backup/moodle2/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/backup/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/backup/util" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/badges/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/blocks/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/blog/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/cache/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/calendar/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/cohort/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/comment/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/competency/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/completion/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/contentbank/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/course/format/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/course/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/customfield/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/enrol/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/favourites/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/files/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/filter/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/grade/grading/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/grade/import/csv/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/grade/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/group/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/h5p/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/iplookup/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/behat/extension" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/ddl/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/dml/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/editor/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/external/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/filebrowser/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/filestorage/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/form/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/grade/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/src/MaxMind/Db" isTestSource="false" packagePrefix="MaxMind\Db\" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/tests/MaxMind/Db/Test/Reader" isTestSource="true" packagePrefix="MaxMind\Db\Test\Reader\" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/php-jwt/src" isTestSource="false" packagePrefix="Firebase\JWT\" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/php-jwt/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/phpunit/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/table/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/testing" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/userkey/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/lib/xapi/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/login/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/message/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/mnet/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/my/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/notes/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/payment/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/plagiarism/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/portfolio/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/privacy/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/question/engine/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/question/engine/upgrade/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/question/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/question/type/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/rating/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/reportbuilder/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/repository/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/rss/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/search/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/tag/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/user/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/moodle/webservice/tests" isTestSource="true" /> + </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> </component> diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000000000000000000000000000000000000..0e09af40726c3560d3f3d145e050377be73a1431 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="PhpProjectSharedConfiguration" php_language_level="7.4"> + <option name="suggestChangeDefaultLanguageLevel" value="false" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8758d74baa5fdbd3f0bb65234d731f31cbbcc63 --- /dev/null +++ b/.idea/phpunit.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="PHPUnit"> + <option name="directories"> + <list> + <option value="$PROJECT_DIR$/moodle/lib/php-jwt/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/maxmind/MaxMind/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/phpunit/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/testing" /> + <option value="$PROJECT_DIR$/moodle/lib/ddl/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/dml/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/tests" /> + <option value="$PROJECT_DIR$/moodle/favourites/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/form/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/filestorage/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/filebrowser/tests" /> + <option value="$PROJECT_DIR$/moodle/files/tests" /> + <option value="$PROJECT_DIR$/moodle/filter/tests" /> + <option value="$PROJECT_DIR$/moodle/admin/roles/tests" /> + <option value="$PROJECT_DIR$/moodle/cohort/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/grade/tests" /> + <option value="$PROJECT_DIR$/moodle/grade/tests" /> + <option value="$PROJECT_DIR$/moodle/grade/grading/tests" /> + <option value="$PROJECT_DIR$/moodle/grade/import/csv/tests" /> + <option value="$PROJECT_DIR$/moodle/analytics/tests" /> + <option value="$PROJECT_DIR$/moodle/availability/tests" /> + <option value="$PROJECT_DIR$/moodle/backup/controller/tests" /> + <option value="$PROJECT_DIR$/moodle/backup/converter/moodle1/tests" /> + <option value="$PROJECT_DIR$/moodle/backup/moodle2/tests" /> + <option value="$PROJECT_DIR$/moodle/backup/tests" /> + <option value="$PROJECT_DIR$/moodle/backup/util" /> + <option value="$PROJECT_DIR$/moodle/badges/tests" /> + <option value="$PROJECT_DIR$/moodle/blog/tests" /> + <option value="$PROJECT_DIR$/moodle/customfield/tests" /> + <option value="$PROJECT_DIR$/moodle/iplookup/tests" /> + <option value="$PROJECT_DIR$/moodle/course/tests" /> + <option value="$PROJECT_DIR$/moodle/course/format/tests" /> + <option value="$PROJECT_DIR$/moodle/privacy/tests" /> + <option value="$PROJECT_DIR$/moodle/question/engine/tests" /> + <option value="$PROJECT_DIR$/moodle/question/tests" /> + <option value="$PROJECT_DIR$/moodle/question/type/tests" /> + <option value="$PROJECT_DIR$/moodle/question/engine/upgrade/tests" /> + <option value="$PROJECT_DIR$/moodle/cache/tests" /> + <option value="$PROJECT_DIR$/moodle/calendar/tests" /> + <option value="$PROJECT_DIR$/moodle/enrol/tests" /> + <option value="$PROJECT_DIR$/moodle/group/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/external/tests" /> + <option value="$PROJECT_DIR$/moodle/message/tests" /> + <option value="$PROJECT_DIR$/moodle/notes/tests" /> + <option value="$PROJECT_DIR$/moodle/tag/tests" /> + <option value="$PROJECT_DIR$/moodle/rating/tests" /> + <option value="$PROJECT_DIR$/moodle/repository/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/userkey/tests" /> + <option value="$PROJECT_DIR$/moodle/user/tests" /> + <option value="$PROJECT_DIR$/moodle/webservice/tests" /> + <option value="$PROJECT_DIR$/moodle/mnet/tests" /> + <option value="$PROJECT_DIR$/moodle/completion/tests" /> + <option value="$PROJECT_DIR$/moodle/comment/tests" /> + <option value="$PROJECT_DIR$/moodle/search/tests" /> + <option value="$PROJECT_DIR$/moodle/competency/tests" /> + <option value="$PROJECT_DIR$/moodle/my/tests" /> + <option value="$PROJECT_DIR$/moodle/auth/tests" /> + <option value="$PROJECT_DIR$/moodle/blocks/tests" /> + <option value="$PROJECT_DIR$/moodle/login/tests" /> + <option value="$PROJECT_DIR$/moodle/plagiarism/tests" /> + <option value="$PROJECT_DIR$/moodle/portfolio/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/editor/tests" /> + <option value="$PROJECT_DIR$/moodle/rss/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/table/tests" /> + <option value="$PROJECT_DIR$/moodle/h5p/tests" /> + <option value="$PROJECT_DIR$/moodle/lib/xapi/tests" /> + <option value="$PROJECT_DIR$/moodle/contentbank/tests" /> + <option value="$PROJECT_DIR$/moodle/payment/tests" /> + <option value="$PROJECT_DIR$/moodle/reportbuilder/tests" /> + <option value="$PROJECT_DIR$/moodle/admin/presets/tests" /> + <option value="$PROJECT_DIR$/moodle/admin/tests" /> + </list> + </option> + </component> +</project> \ No newline at end of file diff --git a/moodle-assignsubmission-autograde/classes/autograde_webservice.php b/moodle-assignsubmission-autograde/classes/autograde_webservice.php index 875aba0e5d7f8f1d8f9b5ea858198e9131f85e70..cc7b905848160faf56c2d8281fceaad19178eef8 100644 --- a/moodle-assignsubmission-autograde/classes/autograde_webservice.php +++ b/moodle-assignsubmission-autograde/classes/autograde_webservice.php @@ -12,9 +12,12 @@ use stdClass; */ final class autograde_webservice { - private $api_key; + private string $api_key; - public function __construct($api_key) { + /** + * @param string $api_key ??? + */ + public function __construct(string $api_key) { $this->api_key = $api_key; } @@ -98,6 +101,46 @@ final class autograde_webservice { return $r; } + /** + * ??? + * @param $courseid ??? + * @param $assignmentid + * @return stdClass ??? + * @throws dml_exception + */ + public function upload_credentials($courseid, $assignmentid): stdClass { + // HR : Build the URL for the grade function + $url = $this->url('/api/v1/registry/credentials/upload'); + // HR : Prepare the request body (JSON) + $body = array( + 'courseid' => $courseid, + 'assignmentid' => $assignmentid, + ); + // HR : Prepare the headers + $headers = [ + $this->api_key_header($this->api_key), + 'Content-Type: application/json' + ]; + // HR : Prepare cURL options + $options = array( + CURLOPT_URL => $url, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($body), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $headers + ); + // HR : Send request using cURL + $curl = curl_init(); + curl_setopt_array($curl, $options); + $response = curl_exec($curl); + curl_close($curl); + // Return the response + $r = new stdClass(); + $r->response = $response; + $r->info = curl_getinfo($curl); + return $r; + } + // ============================================================================================ // =================================== HELPER METHODS ========================================= // ============================================================================================ diff --git a/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php b/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php new file mode 100644 index 0000000000000000000000000000000000000000..ccffa273957db8f0221dc7dcdf2f8be78a14a6cd --- /dev/null +++ b/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php @@ -0,0 +1,90 @@ +<?php + +namespace assignsubmission_autograde\external; + +use coding_exception; +use context_course; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use moodle_exception; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/mod/assign/locallib.php'); + + +/** + * ??? + * + * @author Hamza REMMAL (hamza.remmal@epfl.ch) + */ +final class autograde_download_credentials extends external_api { + + /** + * ??? + * @return external_function_parameters ??? + */ + public static function download_credentials_parameters(): external_function_parameters { + return new external_function_parameters([ + 'courseid' => new external_value(PARAM_INT, 'Course ID'), + 'assignmentid' => new external_value(PARAM_INT, 'Assignment ID') + ]); + } + + /** + * ??? + * @param $courseid ??? + * @param $assignmentid ??? + * @return object ??? + * @throws coding_exception ??? + * @throws moodle_exception ??? + */ + public static function download_credentials($courseid, $assignmentid): object { + $context = context_course::instance($courseid); + $component = 'assignsubmission_autograde'; + $filearea = 'credentials'; + $fs = get_file_storage(); + $files = $fs->get_area_files( + $context->id, + $component, + $filearea, + 0, + 'itemid, filepath, filename', + false + ); + // HR : Check if files are found + if (!empty($files)) { + // HR : Iterate over al the available files + foreach ($files as $file){ + // HR : If we find a non-empty file, return it + // HR : We allow only one file for now + if(!empty($file->get_content())){ + // HR : Return the base64 encoding of the file's content + return (object) [ + 'content' => base64_encode($file->get_content()) + ]; + } + } + } else { + // No files found, return an error + throw new moodle_exception('nofilefound', 'autograde'); + } + throw new moodle_exception('all files are empty', 'autograde'); + } + + /** + * ??? + * @return external_single_structure ??? + */ + public static function download_credentials_returns(): external_single_structure{ + return new external_single_structure([ + 'content' => new external_value(PARAM_TEXT, 'File Content') + ]); + } + +} \ No newline at end of file diff --git a/moodle-assignsubmission-autograde/db/services.php b/moodle-assignsubmission-autograde/db/services.php index 1c59d829d48bed026965093ec1ee8e1c7add5de5..5b5181e592aa0ee122772544b0255359d119c051 100644 --- a/moodle-assignsubmission-autograde/db/services.php +++ b/moodle-assignsubmission-autograde/db/services.php @@ -18,6 +18,12 @@ $functions = [ 'description' => 'Download an autograde submission', //'capabilities' => '', ], + 'mod_assignsubmission_autograde_download_credentials' => [ + 'classname' => 'assignsubmission_autograde\external\autograde_download_credentials', + 'methodname' => 'download_credentials', + 'description' => 'Download an autograde credential', + //'capabilities' => '', + ], ]; // https://moodledev.io/docs/apis/subsystems/external/advanced/custom-services @@ -26,7 +32,8 @@ $services = [ 'AUTOGRADE' => [ 'functions' => [ 'mod_assignsubmission_autograde_upload_feedback', - 'mod_assignsubmission_autograde_download_submission' + 'mod_assignsubmission_autograde_download_submission', + 'mod_assignsubmission_autograde_download_credentials' ], 'restrictedusers' => 1, 'enabled' => 1, diff --git a/moodle-assignsubmission-autograde/locallib.php b/moodle-assignsubmission-autograde/locallib.php index 7ef0c5a21f4b5dc2e071738901d75ea041ba33d4..29d41f201ac6bc7d04ccf2d77c12f12a9626c06b 100644 --- a/moodle-assignsubmission-autograde/locallib.php +++ b/moodle-assignsubmission-autograde/locallib.php @@ -7,6 +7,8 @@ defined('MOODLE_INTERNAL') || die(); const ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA = 'submissions'; +const ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA = 'credentials'; + /** * Autograde Submission plugin. * @@ -65,24 +67,17 @@ final class assign_submission_autograde extends assign_submission_plugin { 'assignsubmission_autograde_enabled', 'notchecked'); - // HR : Add text input to store the docker registry token - $mform->addElement( - "text", - self::setting_docker_registry_token_id, - get_string("setting_docker-registry-token", self::COMPONENT_NAME), - null); - $mform->addHelpButton( - self::setting_docker_registry_token_id, - "setting_docker-registry-token", - self::COMPONENT_NAME); + // HR : Add filemanager to store the docker registry token + $credentials_options = $this->get_credentials_option(); + $mform->addElement("filemanager", "credentials", "Registry Credentials", null, $credentials_options); $mform->hideIf( - self::setting_docker_registry_token_id, + 'credentials', 'assignsubmission_autograde_enabled', 'notchecked'); // HR : Add entry to store the API_KEY $mform->addElement( - "text", + "password", self::setting_webservice_key_id, get_string("setting_webservice-key", self::COMPONENT_NAME), null); @@ -102,30 +97,41 @@ final class assign_submission_autograde extends assign_submission_plugin { * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#save_settings * @param stdClass $data ??? * @return bool ??? - * @throws dml_exception ??? + * @throws coding_exception + * @throws dml_exception */ public function save_settings(stdClass $data): bool { // HR : Store the Docker image in the configuration + global $COURSE; $this->set_config(self::setting_docker_image_id, $data->docker_image); - // HR : Store the Docker registry token in the configuration - $this->set_config(self::setting_docker_registry_token_id, $data->docker_registry_token); // HR : Store the WebService API_KEY in the configuration setting $this->set_config(self::setting_webservice_key_id, $data->webservice_key); - // HR : Ping the server with the API_KEY to verify that the key is working - // NOTE HR : a timeout need to be set, and it should be small so that this function doesn't - // block that long + // HR : Save the credentials in the corresponding file area + file_save_draft_area_files( + $data->credentials, + context_course::instance($COURSE->id)->id, + self::COMPONENT_NAME, + ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA, + 0 + ); + + // HR : Send request to the autograde service to update the credentials + // HR : also test the API_KEY $autograde_webservice = new autograde_webservice($this->get_config(self::setting_webservice_key_id)); - $response = $autograde_webservice->ping(1, true); + $response = $autograde_webservice->upload_credentials($COURSE->id, $this->assignment->get_instance()->id); // HR : Analyze the response - if($response === false){ - $this->set_error('Request failed (Server not responding)!'); - return false; - } else if ($response['http_code'] == 200){ - notification::info('The provided API_KEY is valid !'); - } else if ($response['http_code'] == 403){ + if ($response->info['http_code'] == 200){ + notification::info('The credentials were correctly updated in the service'); + } else if ($response->info['http_code'] == 403) { $this->set_error('The provided API_KEY is not valid !'); return false; + } else if($response->info['http_code'] == 500){ + $this->set_error("internal error : " . $response->response); + return false; + } else if($response->info['http_code'] == 0){ + $this->set_error("autograde service not found. please contact the support."); + return false; } return true; } @@ -136,7 +142,26 @@ final class assign_submission_autograde extends assign_submission_plugin { * @return void ??? */ public function data_preprocessing(&$defaultvalues) { - // TODO HR : Implement this function + // HR : Call the parent function + global $COURSE; + parent::data_preprocessing($defaultvalues); + // HR : Fill in the docker image setting if it was previously filled + $defaultvalues[self::setting_docker_image_id] = + $this->get_config(self::setting_docker_image_id); + // HR : Fill in the API-KEY setting if it was previously filled + $defaultvalues[self::setting_webservice_key_id] = + $this->get_config(self::setting_webservice_key_id); + + $draftitemid = file_get_submitted_draft_itemid('credentials'); + file_prepare_draft_area( + $draftitemid, + context_course::instance($COURSE->id)->id, + self::COMPONENT_NAME, + ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA, + 0, + $this->get_credentials_option() + ); + $defaultvalues['credentials'] = $draftitemid; } // ============================================================================================ @@ -160,7 +185,7 @@ final class assign_submission_autograde extends assign_submission_plugin { $userid): bool { global $COURSE; // HR : Accepted files - $fileoptions = $this->get_file_options(); + $fileoptions = $this->get_submission_options(); // HR : Prepare the filemanager for the submission $data = file_prepare_standard_filemanager( @@ -200,7 +225,7 @@ final class assign_submission_autograde extends assign_submission_plugin { // HR : Without this step, the download_submission webservice won't work $data = file_postupdate_standard_filemanager($data, 'tasks', - $this->get_file_options(), + $this->get_submission_options(), context_course::instance($COURSE->id), self::COMPONENT_NAME, ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA, @@ -270,10 +295,14 @@ final class assign_submission_autograde extends assign_submission_plugin { return $result; } + /** + * ??? + * @return string[] ??? + */ public function get_file_areas(): array { - // TODO HR : Implement this function return array( - ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA => "store the submissions" + ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA => "store the submissions", + ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA => "store the credentials" ); } @@ -285,10 +314,9 @@ final class assign_submission_autograde extends assign_submission_plugin { * View of the summary (visible by the student) * * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#view_summary - * @param stdClass $submissionorgrade - * @param bool &$showviewlink - * @return string - * @throws coding_exception + * @param $submission ??? + * @param bool &$showviewlink ??? + * @return string ??? */ public function view_summary($submission, &$showviewlink): string { // TODO HR : 1- Fetch the path to the file to render from the database @@ -342,7 +370,7 @@ final class assign_submission_autograde extends assign_submission_plugin { * * @return array ??? */ - private function get_file_options(): array{ + private function get_submission_options(): array{ return array( 'subdirs' => 1, "maxfiles" => 1, @@ -351,4 +379,13 @@ final class assign_submission_autograde extends assign_submission_plugin { ); } + private function get_credentials_option() : array { + return array( + 'subdirs' => 1, + "maxfiles" => 1, + 'accepted_types' => array(".config"), + 'return_types' => FILE_INTERNAL | FILE_EXTERNAL + ); + } + } \ No newline at end of file diff --git a/moodle-assignsubmission-autograde/settings.php b/moodle-assignsubmission-autograde/settings.php index 02330bb5a959ee43d9ba5a5df23123981fbb557d..a815f99fd1a80db90b56d30cf17bf44f954c4824 100644 --- a/moodle-assignsubmission-autograde/settings.php +++ b/moodle-assignsubmission-autograde/settings.php @@ -5,6 +5,6 @@ defined('MOODLE_INTERNAL') || die(); $settings->add( new admin_setting_configtext("assignsubmission_autograde/service_url", "Service URL", - "URL to the kubernetes cluster hosting the service", + "URL to the autograde service", "") ); \ No newline at end of file diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java new file mode 100644 index 0000000000000000000000000000000000000000..c02c41a13b73d2c0d1c512a5ec1eea35e752a12e --- /dev/null +++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java @@ -0,0 +1,52 @@ +package ch.epfl.cs107.grading.moodle.api.v1.controller; + +import ch.epfl.cs107.grading.moodle.api.v1.dto.UploadCredentialsDTO; +import ch.epfl.cs107.grading.moodle.api.v1.service.KubernetesJobService; +import ch.epfl.cs107.grading.moodle.api.v1.service.MoodleWebService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * ??? + * + * @author Hamza REMMAL (hamza.remmal@epfl.ch) + */ +@RestController +@RequestMapping("/api/v1/registry/credentials") +public final class RegistryCredentialsController { + + /** ??? */ + private final KubernetesJobService k8s; + + /** ??? */ + private final MoodleWebService moodle; + + /** + * ??? + * @param k8s ??? + */ + @Autowired + public RegistryCredentialsController(KubernetesJobService k8s, MoodleWebService moodle) { + this.k8s = k8s; + this.moodle = moodle; + } + + /** + * ??? + * @return ??? + */ + @PostMapping("/upload") + public ResponseEntity<?> uploadCredentials(@RequestBody UploadCredentialsDTO credentials){ + try(var cred = moodle.download_credentials(credentials.getCourseid(), credentials.getAssignmentid())){ + // TODO : Use k8s to store the credentials in the corresponding namespace in the cluster + return ResponseEntity.ok().body(null); + } catch (Exception e){ + return ResponseEntity.internalServerError().body(e); + } + } + +} diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..ebb9d6f3923d24815460e37de43c398c57b3c6b1 --- /dev/null +++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java @@ -0,0 +1,23 @@ +package ch.epfl.cs107.grading.moodle.api.v1.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * ??? + * + * @author Hamza REMMAL (hamza.remmal@epfl.ch) + */ +@Data +public class DownloadCredentialsDTO { + + /** ??? */ + private final String content; + + @JsonCreator + public DownloadCredentialsDTO(@JsonProperty("content") String content) { + this.content = content; + } + +} diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..e1c2bd78fd993d3614bed8bd3200c8f5203cc26d --- /dev/null +++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java @@ -0,0 +1,19 @@ +package ch.epfl.cs107.grading.moodle.api.v1.dto; + +import lombok.Data; + +/** + * ??? + * + * @author Hamza REMMAL (hamza.remmal@epfl.ch) + */ +@Data +public final class UploadCredentialsDTO { + + /** ???? */ + private final int courseid; + + private final int assignmentid; + + +} diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java index 6aaffa67d44834bb1b0e3246d74f099f969811de..8aba00212b68d2fe3b191d6f067161b976449fd8 100644 --- a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java +++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java @@ -1,5 +1,6 @@ package ch.epfl.cs107.grading.moodle.api.v1.service; +import ch.epfl.cs107.grading.moodle.api.v1.dto.DownloadCredentialsDTO; import ch.epfl.cs107.grading.moodle.api.v1.dto.DownloadSubmissionDTO; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; @@ -154,4 +155,34 @@ public final class MoodleWebService { } } + /** + * ??? + * @param courseid ??? + * @param assignmentid ??? + * @return ??? + * @throws URISyntaxException ??? + * @throws IOException ??? + * @throws InterruptedException ??? + */ + public InputStream download_credentials(int courseid, int assignmentid) throws URISyntaxException, IOException, InterruptedException { + final var FUNCTION_NAME = "mod_assignsubmission_autograde_download_credentials"; + + final var params = new HashMap<String, Object>(); + params.put("courseid", courseid); + params.put("assignmentid", assignmentid); + + var response = call(FUNCTION_NAME, params, ofString()); + + System.out.println(response.body()); + + var objectMapper = new ObjectMapper(); + var submission = objectMapper.readValue(response.body(), DownloadCredentialsDTO.class); + + if(response.statusCode() != HttpStatus.OK.value()){ + throw new IllegalStateException(submission.getContent()); + } else { + return new ByteArrayInputStream(Base64.getDecoder().decode(submission.getContent())); + } + } + }