diff --git a/deploy/docker/compose/src/main/compose/2_connector-common.yml b/deploy/docker/compose/src/main/compose/2_connector-common.yml index 72dd261..fb939f2 100644 --- a/deploy/docker/compose/src/main/compose/2_connector-common.yml +++ b/deploy/docker/compose/src/main/compose/2_connector-common.yml @@ -26,6 +26,14 @@ services: DATABASE_NAME: "${SERVICES_EDU_CONNECTOR_DATABASE_NAME:-connector}" DATABASE_USER: "${SERVICES_EDU_CONNECTOR_DATABASE_USER:-connector}" DATABASE_PASSWORD: "${SERVICES_EDU_CONNECTOR_DATABASE_PASS:-connector}" + ONLYOFFICE_DOCUMENT_SERVER: "${SERVICES_EDU_CONNECTOR_ONLYOFFICE_DOCUMENT_SERVER:-}" + ONLYOFFICE_PLUGIN_URL: "${SERVICES_EDU_CONNECTOR_ONLYOFFICE_PLUGIN_URL:-}" + ONLYOFFICE_JWT_SECRET: "${SERVICES_EDU_CONNECTOR_ONLYOFFICE_JWT_SECRET:-}" + UPLOAD_FILE_SIZE: "${SERVICES_EDU_CONNECTOR_UPLOAD_FILE_SIZE:-512M}" + POST_MAX_SIZE: "${SERVICES_EDU_CONNECTOR_POST_MAX_SIZE:-513M}" + MEMORY_LIMIT: "${SERVICES_EDU_CONNECTOR_MEMORY_LIMIT:-1024M}" + MOODLE_BASE_DIR: "${SERVICES_EDU_CONNECTOR_MOODLE_BASE_DIR:-}" + MOODLE_TOKEN: "${SERVICES_EDU_CONNECTOR_MOODLE_TOKEN:-}" volumes: - "services-edu-connector-data:/var/data" depends_on: diff --git a/deploy/docker/compose/src/main/compose/2_connector-remote.yml b/deploy/docker/compose/src/main/compose/2_connector-remote.yml index 7a06a4a..a8ec991 100644 --- a/deploy/docker/compose/src/main/compose/2_connector-remote.yml +++ b/deploy/docker/compose/src/main/compose/2_connector-remote.yml @@ -3,7 +3,7 @@ version: '3.7' services: services-edu-connector-database: - image: "${docker.registry}/${docker.repository}/${docker.prefix}-deploy-docker-build-postgres:${docker.tag}" + image: "${docker.registry}/${docker.repository}/${docker.prefix}-deploy-docker-build-postgresql:${docker.tag}" services-edu-connector-service: image: "${docker.registry}/community/${docker.prefix}-service:${docker.tag}" diff --git a/deploy/docker/helm/src/main/chart/Chart.yaml b/deploy/docker/helm/src/main/chart/Chart.yaml index 88051fe..3083801 100644 --- a/deploy/docker/helm/src/main/chart/Chart.yaml +++ b/deploy/docker/helm/src/main/chart/Chart.yaml @@ -3,5 +3,5 @@ apiVersion: v2 name: edu-sharing-services-connector description: Helm chart for edu-sharing connector app type: application -version: 0.1.0 +version: 8.0.9998 appVersion: 0.1.0 \ No newline at end of file diff --git a/deploy/docker/helm/src/main/chart/templates/statefulset.yml b/deploy/docker/helm/src/main/chart/templates/statefulset.yml index 0ab6f2c..ce7cd8a 100644 --- a/deploy/docker/helm/src/main/chart/templates/statefulset.yml +++ b/deploy/docker/helm/src/main/chart/templates/statefulset.yml @@ -36,6 +36,20 @@ spec: value: {{ required "A valid .Values.passwordDB is required!" .Values.passwordDB }} - name: ONLYOFFICE_DOCUMENT_SERVER value: {{ .Values.onlyofficeDocumentServer | default "" }} + - name: ONLYOFFICE_PLUGIN_URL + value: {{ .Values.onlyofficePluginUrl | default "" }} + - name: ONLYOFFICE_JWT_SECRET + value: {{ .Values.onlyofficeJwtSecret | default "" }} + - name: MOODLE_BASE_DIR + value: {{ .Values.moodleBaseDir | default "" }} + - name: MOODLE_TOKEN + value: {{ .Values.moodleToken | default "" }} + - name: UPLOAD_FILE_SIZE + value: {{ .Values.uploadFileSize | default "512M" }} + - name: POST_MAX_SIZE + value: {{ .Values.postMaxSize | default "513M" }} + - name: MEMORY_LIMIT + value: {{ .Values.memoryLimit | "1024M" }} livenessProbe: httpGet: path: /metadata diff --git a/deploy/docker/helm/src/main/chart/values.yaml b/deploy/docker/helm/src/main/chart/values.yaml index 05db910..839a8c9 100644 --- a/deploy/docker/helm/src/main/chart/values.yaml +++ b/deploy/docker/helm/src/main/chart/values.yaml @@ -1,4 +1,4 @@ baseUrl: storageClassName: clusterIssuer: letsencrypt -version: maven-fixes-7.0-SNAPSHOT +version: maven-fixes-8.0-SNAPSHOT diff --git a/service/Dockerfile b/service/Dockerfile index d5bca27..d7ae033 100755 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -86,7 +86,8 @@ RUN set -eux \ && adduser www-data appuser RUN set -eux \ - && chown -R appuser:appuser /etc/apache2 $ROOT $DATA + && chown -R appuser:appuser /etc/apache2 $ROOT $DATA \ + && chown -R appuser:appuser $PHP_INI_DIR USER appuser diff --git a/service/src/main/docker/entrypoint.sh b/service/src/main/docker/entrypoint.sh index 650c831..2336a72 100644 --- a/service/src/main/docker/entrypoint.sh +++ b/service/src/main/docker/entrypoint.sh @@ -23,6 +23,10 @@ connector_database_name=${DATABASE_NAME//\/&/\\&} # OPTIONALS +upload_max_filesize="${UPLOAD_FILE_SIZE:-512M}" +post_max_size="${POST_MAX_SIZE:-513M}" +memory_limit="${MEMORY_LIMIT:-1024M}" + only_office_document_server="${ONLYOFFICE_DOCUMENT_SERVER:-}" only_office_plugin_url="${ONLYOFFICE_PLUGIN_URL:-}" only_office_jwt_secret="${ONLYOFFICE_JWT_SECRET:-}" @@ -32,6 +36,8 @@ moodle_base_dir="${MOODLE_BASE_DIR:-}" # shellcheck disable=SC2153 moodle_token="${MOODLE_TOKEN:-}" +php_ini=$PHP_INI_DIR/php.ini + sed -i "s|define('WWWURL', '.*')|define('WWWURL', '${connector_url}')|g" "${conf}" sed -i "s|define('DOCROOT', '.*')|define('DOCROOT', '${ROOT}')|g" "${conf}" sed -i "s|define('DATA', '.*')|define('DATA', '${DATA}')|g" "${conf}" @@ -43,6 +49,10 @@ sed -i "s|define('DBUSER', '.*')|define('DBUSER', '${connector_database_user}')| sed -i "s|define('DBPASSWORD', '.*')|define('DBPASSWORD', '${connector_database_password}')|g" "${conf}" sed -i "s|define('DBNAME', '.*')|define('DBNAME', '${connector_database_name}')|g" "${conf}" +sed -i -r "s|upload_max_filesize.*|upload_max_filesize = ${upload_max_filesize}|" "${php_ini}" +sed -i -r "s|post_max_size.*|post_max_size = ${post_max_size}|" "${php_ini}" +sed -i -r "s|memory_limit.*|memory_limit = ${memory_limit}|" "${php_ini}" + sed -i "s|define('ONLYOFFICE_DOCUMENT_SERVER', '.*')|define('ONLYOFFICE_DOCUMENT_SERVER', '${only_office_document_server}')|g" "${conf}" sed -i "s|define('ONLYOFFICE_PLUGIN_URL', '.*')|define('ONLYOFFICE_PLUGIN_URL', '${only_office_plugin_url}')|g" "${conf}" sed -i "s|define('ONLYOFFICE_JWT_SECRET', '.*')|define('ONLYOFFICE_JWT_SECRET', '${only_office_jwt_secret}')|g" "${conf}" diff --git a/service/src/main/php/bootstrap.php b/service/src/main/php/bootstrap.php index b1504f2..a01aed0 100644 --- a/service/src/main/php/bootstrap.php +++ b/service/src/main/php/bootstrap.php @@ -1,17 +1,20 @@ [ +$container = new Container; +$app = new App([$container, 'settings' => [ 'displayErrorDetails' => true, 'debug' => true, 'whoops.editor' => 'sublime', @@ -31,6 +34,8 @@ return $view; }; +$app->add(new BlockingMiddleware()); + $app->get('/', function (Request $request, Response $response) { $this->get('log')->info($request->getUri()); $connector = new Connector($this->get('log'), $this, $response); diff --git a/service/src/main/php/cacheCleaner.php b/service/src/main/php/cacheCleaner.php new file mode 100644 index 0000000..ad4b47b --- /dev/null +++ b/service/src/main/php/cacheCleaner.php @@ -0,0 +1,8 @@ +run(); diff --git a/service/src/main/php/src/lib/BlockingMiddleware.php b/service/src/main/php/src/lib/BlockingMiddleware.php new file mode 100644 index 0000000..bad920b --- /dev/null +++ b/service/src/main/php/src/lib/BlockingMiddleware.php @@ -0,0 +1,41 @@ + + */ +class BlockingMiddleware +{ + /** + * Function __invoke + * + * Function to run the middleware as per the Slim PHP documentation + * + * @param Request $request + * @param Response $response + * @param callable $next + * @return Response + */ + public function __invoke(Request $request, Response $response, callable $next): Response + { + $retryCount = 0; + while (file_exists(__DIR__ . '/' . CacheCleaner::LOCK_FILE_NAME) && $retryCount <= 10) { + sleep(1); + $retryCount += 1; + } + if (file_exists(__DIR__ . '/' . CacheCleaner::LOCK_FILE_NAME)) { + return $response->withStatus(503); + } + return $next($request, $response); + } +} diff --git a/service/src/main/php/src/lib/CacheCleaner.php b/service/src/main/php/src/lib/CacheCleaner.php new file mode 100644 index 0000000..e074519 --- /dev/null +++ b/service/src/main/php/src/lib/CacheCleaner.php @@ -0,0 +1,136 @@ + + */ +class CacheCleaner +{ + public const LOCK_FILE_NAME = 'LOCK'; + private MonoLogger $logger; + private Database $database; + private H5P $h5p; + + /** + * CacheCleaner constructor + */ + public function __construct() { + $this->init(); + } + + /** + * Function init + * + * sets up dependencies + */ + private function init(): void { + $logInitializer = new Logger(); + $this->logger = $logInitializer->getLog(); + $this->database = new Database(); + $this->h5p = new H5P(); + } + + /** + * Function run + * + * This is the main workhorse of the class + * + * @return void + */ + public function run(): void { + $this->logger->info('### Cache cleaner started ###'); + try { + $this->lock(); + $this->database->beginTransaction(); + $this->clearH5pTables(); + $this->clearH5pDirectories(); + ! $this->database->commit() && throw new Exception('### Database commit failed. Script terminated ###'); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + $this->logger->error('### Database transaction rollback started. ###'); + $this->database->rollBack(); + } finally { + try { + $this->unlock(); + $this->logger->info('### Cache cleaner ran successfully ###'); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + } + } + + /** + * Function clearH5pTables + * + * clears H5P-related tables + * + * @throws Exception + */ + private function clearH5pTables(): void { + $libraryLanguageRes = $this->database->query('TRUNCATE TABLE h5p_libraries_languages'); + $contentsLibrariesRes = $this->database->query('TRUNCATE TABLE h5p_contents_libraries'); + $contentsRes = $this->database->query('TRUNCATE TABLE h5p_contents'); + $librariesRes = $this->database->query('TRUNCATE TABLE h5p_libraries'); + $librariesLanguagesRes = $this->database->query('TRUNCATE TABLE h5p_libraries_languages'); + $librariesLibrariesRes = $this->database->query('TRUNCATE TABLE h5p_libraries_libraries'); + if (in_array(false, [$libraryLanguageRes, $contentsLibrariesRes, $contentsRes, $librariesRes, $librariesLanguagesRes, $librariesLibrariesRes], true)) { + throw new Exception('### Cache cleaner error: Database operation failed. Script will be terminated. ###'); + } + } + + /** + * Function clearH5pDirectories + * + * deletes all files and folders from the H5P-related directories within the cache directory + * + * @throws Exception + */ + private function clearH5pDirectories(): void { + $directories = ['content', 'exports', 'libraries', 'editor', 'temp']; + try { + foreach ($directories as $directory) { + $this->h5p->rrmdir($this->h5p->H5PFramework->get_h5p_path() . '/' . $directory, true); + } + } catch (Exception $exception) { + throw new Exception('### Cache cleaner error: ' . $exception->getMessage() . '. Script will be terminated. ###'); + } + } + + /** + * Function lock + * + * creates a lock file in order to lock the connector service + * for the duration of the cache cleaning process + * + * @throws Exception + */ + private function lock(): void { + $lockFile = fopen(static::LOCK_FILE_NAME, "w"); + $lockFile === false && throw new Exception('### Cache cleaner error: Cannot create lock file. Script will be terminated. ###'); + } + + /** + * Function unlock + * + * deletes the lock file in order to unlock the connector service + * + * @throws Exception + */ + private function unlock(): void { + $isFileDeleted = unlink(static::LOCK_FILE_NAME); + ! $isFileDeleted && throw new Exception('### Cache cleaner error: Cannot delete lock file. Script will be terminated. ###'); + } +} diff --git a/service/src/main/php/src/lib/EduRestClient.php b/service/src/main/php/src/lib/EduRestClient.php index c817e5c..9ae4a52 100644 --- a/service/src/main/php/src/lib/EduRestClient.php +++ b/service/src/main/php/src/lib/EduRestClient.php @@ -2,6 +2,8 @@ namespace connector\lib; +use connector\tools\h5p\H5PFramework; + define('APPID', 'educonnector'); class EduRestClient @@ -93,7 +95,7 @@ public function unlockNode($nodeId) { throw new \Exception('Error unlocking node ' . $nodeId, $httpcode); } - public function getContent($node, $downloadUrl=NULL){ + public function getContent($node, $downloadUrl = null, $isH5p = false){ if ($node->node->contentUrl){ $contentUrl = $node->node->contentUrl; //repo-version 5.0 or older }else{ @@ -122,7 +124,14 @@ public function getContent($node, $downloadUrl=NULL){ $url = $contentUrl . '&ticket=' . $_SESSION[$this->connectorId]['ticket'] . '¶ms=display%3Ddownload'; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (! $isH5p) { + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + } else { + $h5pFramework = new H5PFramework(); + $path = $h5pFramework->getUploadedH5pPath(); + $filePath = fopen($path, 'wb'); + curl_setopt($curl,CURLOPT_FILE, $filePath); + } curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); @@ -130,6 +139,9 @@ public function getContent($node, $downloadUrl=NULL){ $data = curl_exec($curl); $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); + if ($isH5p) { + fclose($filePath); + } if ($httpcode >= 200 && $httpcode < 308) { return $data; }else{ diff --git a/service/src/main/php/src/lib/Logger.php b/service/src/main/php/src/lib/Logger.php index 4df3994..13d7d40 100644 --- a/service/src/main/php/src/lib/Logger.php +++ b/service/src/main/php/src/lib/Logger.php @@ -2,30 +2,35 @@ namespace connector\lib; +use Exception; use Monolog\Handler\RedisHandler; use Monolog\Formatter\LogstashFormatter; +use Monolog\Handler\RotatingFileHandler; +use Monolog\Handler\StreamHandler; +use Monolog\Logger as MonoLogger; +use Monolog\Processor\IntrospectionProcessor; use Predis\Client; class Logger { - private $log; + private MonoLogger $log; public function __construct() { - $this->log = new \Monolog\Logger('eduConnector'); - $this->log->pushProcessor(new \Monolog\Processor\IntrospectionProcessor()); + $this->log = new MonoLogger('eduConnector'); + $this->log->pushProcessor(new IntrospectionProcessor()); /* * Log to local file * */ if(LOG_MODE === 'file') { - $this->log->pushHandler(new \Monolog\Handler\RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'error.log', 0, \Monolog\Logger::ERROR)); - $this->log->pushHandler(new \Monolog\Handler\RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'info.log', 0, \Monolog\Logger::INFO)); + $this->log->pushHandler(new RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'error.log', 0, MonoLogger::ERROR)); + $this->log->pushHandler(new RotatingFileHandler(DATA . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'info.log', 0, MonoLogger::INFO)); } else if(LOG_MODE === 'stdout') { - $this->log->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO)); - $this->log->pushHandler(new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Logger::ERROR)); + $this->log->pushHandler(new StreamHandler('php://stdout', MonoLogger::INFO)); + $this->log->pushHandler(new StreamHandler('php://stderr', MonoLogger::ERROR)); } else { - throw new \Exception("invalid LOG_MODE: " . LOG_MODE); + throw new Exception("invalid LOG_MODE: " . LOG_MODE); } $this->log->info("Logger started in mode " . LOG_MODE); /* diff --git a/service/src/main/php/src/tools/h5p/H5P.php b/service/src/main/php/src/tools/h5p/H5P.php index d336f9c..914534a 100644 --- a/service/src/main/php/src/tools/h5p/H5P.php +++ b/service/src/main/php/src/tools/h5p/H5P.php @@ -3,7 +3,7 @@ use connector\lib\Database; use connector\lib\EduRestClient; -use Dompdf\Exception; +use Exception; use Slim\Http\Response; define('MODE_NEW', 'mode_new'); @@ -140,18 +140,22 @@ private function copyr($source, $dest) { } - public function rrmdir($dir) { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { + /** + * @throws Exception + */ + public function rrmdir(string $dir, bool $throwException = false): void { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { if ($object != "." && $object != "..") { - if (is_dir($dir."/".$object)) - $this -> rrmdir($dir."/".$object); - else - unlink($dir."/".$object); - } + if (is_dir($dir."/".$object)) + $this -> rrmdir($dir."/".$object); + else + unlink($dir."/".$object); + } } - rmdir($dir); + $isDirDeleted = rmdir($dir); + ! $isDirDeleted && $throwException && throw new Exception('Cannot delete directory: ' . $dir . '.'); } } @@ -160,7 +164,7 @@ public function showEditor($content, Response $response) { $integration = array(); $integration['baseUrl'] = WWWURL; //$integration['url'] = '/eduConnector/src/tools/h5p'; - $integration['url'] = '/eduConnector/src/tools/h5p/cache'; + $integration['url'] = WWWURL . '/src/tools/h5p/cache'; $integration['siteUrl'] = WWWURL; $integration['postUserStatistics'] = ''; $integration['ajax'] = array(); @@ -270,11 +274,7 @@ public function setNode() { } } else { $client = new EduRestClient($this->connectorId); - $data = $client->getContent($node); - - $fp = fopen($this->H5PFramework->getUploadedH5pPath(), 'w'); - fwrite($fp, $data); - fclose($fp); + $client->getContent($node, null, true); } $_SESSION[$this->connectorId]['node'] = $node; } diff --git a/service/src/main/php/src/tools/h5p/H5PFramework.php b/service/src/main/php/src/tools/h5p/H5PFramework.php index b802954..b159bb7 100644 --- a/service/src/main/php/src/tools/h5p/H5PFramework.php +++ b/service/src/main/php/src/tools/h5p/H5PFramework.php @@ -307,7 +307,11 @@ public function getLibraryId($machineName, $majorVersion = NULL, $minorVersion = */ public function getWhitelist($isLibrary, $defaultContentWhitelist, $defaultLibraryWhitelist) { - return ''; + if ($isLibrary) { + return $defaultLibraryWhitelist; + } else { + return $defaultContentWhitelist; + } } /** diff --git a/service/src/main/php/src/tools/h5p/cache/.htaccess b/service/src/main/php/src/tools/h5p/cache/.htaccess index fef6ab8..84bf4d9 100644 --- a/service/src/main/php/src/tools/h5p/cache/.htaccess +++ b/service/src/main/php/src/tools/h5p/cache/.htaccess @@ -2,4 +2,4 @@ Options FollowSymLinks RewriteEngine On -RewriteRule ^.* ../redirect.php?ID=%{REQUEST_URI}&%{QUERY_STRING} +RewriteRule (^.*) ../redirect.php?ID=%{THE_REQUEST} [PT,B,QSA] \ No newline at end of file diff --git a/service/src/main/php/src/tools/h5p/redirect.php b/service/src/main/php/src/tools/h5p/redirect.php index 1f33c14..b3ab0d8 100644 --- a/service/src/main/php/src/tools/h5p/redirect.php +++ b/service/src/main/php/src/tools/h5p/redirect.php @@ -6,8 +6,13 @@ $connector_name = basename(WWWURL); $base = DATA.'/h5p'; -$src_file = str_replace('/'.$connector_name.'/src/tools/h5p/cache', $base, $_REQUEST['ID']); -$src_file = str_replace('/src/tools/h5p/cache', $base, $_REQUEST['ID']); + +$wholeRequest = $_REQUEST['ID']; +$requestUri = explode(' ', $wholeRequest)[1]; +$oldPath = explode('?', $requestUri)[0]; + +$src_file = str_replace('/'.$connector_name.'/src/tools/h5p/cache', $base, $oldPath); +$src_file = str_replace('/src/tools/h5p/cache', $base, $oldPath); $realPath = realpath($src_file); if($realPath === false || strpos($realPath, $base) !== 0) { diff --git a/service/src/main/php/src/tools/onlyoffice/config.php b/service/src/main/php/src/tools/onlyoffice/config.php new file mode 100644 index 0000000..6044028 --- /dev/null +++ b/service/src/main/php/src/tools/onlyoffice/config.php @@ -0,0 +1,47 @@ + \ No newline at end of file