diff --git a/.gitignore b/.gitignore index 4324850d..734933b7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ volumes/ *.crt *.key *.pem +.jwks diff --git a/front_end/openVRE/apache/server.conf b/front_end/openVRE/apache/server.conf index 1d94da1a..bcc82fff 100644 --- a/front_end/openVRE/apache/server.conf +++ b/front_end/openVRE/apache/server.conf @@ -4,6 +4,11 @@ DocumentRoot /var/www/html/openVRE/public/ DirectoryIndex index.php + + AllowOverride All + Require all granted + + RewriteEngine On RewriteRule ^/interactive-tool/([^/]+)/?(.*) http://$1:8090/$2 [P,L] diff --git a/front_end/openVRE/composer.json b/front_end/openVRE/composer.json index 0e3737a2..c5c7ef9c 100644 --- a/front_end/openVRE/composer.json +++ b/front_end/openVRE/composer.json @@ -1,41 +1,43 @@ { - "require": { - "auth0/auth0-php": "^5.0", - "cache/mongodb-adapter": "^1.1", - "jenssegers/mongodb": "^3.8", - "justinrainbow/json-schema": "^5.2", - "league/oauth2-google": "^2.0", - "mongodb/mongodb": "^1.11", - "phpseclib/phpseclib": "~3.0", - "swaggest/json-schema": "^0.12.34", - "guzzlehttp/guzzle": "*", - "ext-openssl" : "*", - "slim/slim": "4.*", - "slim/extras": "*", - "slim/http": "^1.3.0", - "slim/psr7": "^1.6.0", - "monolog/monolog": "^2.5", - "chadicus/slim-oauth2": "^3.1", - "zircote/swagger-php": "^2.0", - "squizlabs/php_codesniffer": "^3.5" - }, - "autoload": { - "psr-4": { - "App\\": "app/", - "App\\Controllers\\": "app/controllers", - "App\\Models\\": "app/models", - "App\\Middleware\\": "app/Middleware", - "App\\Handlers\\": "app/Handlers" - } - }, - "autoload-dev": { - "psr-4": { - "App\\Test\\": "tests" - } - }, - "config": { - "sort-packages": true - }, - "minimum-stability": "dev", - "prefer-stable": true + "require": { + "ext-openssl": "*", + "auth0/auth0-php": "^8.0", + "chadicus/slim-oauth2": "^3.1", + "firebase/php-jwt": "^6.11", + "guzzlehttp/guzzle": "*", + "justinrainbow/json-schema": "^5.2", + "league/oauth2-google": "^2.0", + "mongodb/mongodb": "^2.1", + "monolog/monolog": "^2.5", + "phpseclib/phpseclib": "~3.0", + "slim/extras": "*", + "slim/http": "^1.3.0", + "slim/psr7": "^1.6.0", + "slim/slim": "4.*", + "squizlabs/php_codesniffer": "^3.5", + "swaggest/json-schema": "^0.12.34", + "zircote/swagger-php": "^2.0" + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "App\\Controllers\\": "app/controllers", + "App\\Models\\": "app/models", + "App\\Middleware\\": "app/Middleware", + "App\\Handlers\\": "app/Handlers" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Test\\": "tests" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/front_end/openVRE/public/api/.htaccess b/front_end/openVRE/public/api/.htaccess index 9754f587..08ed1600 100644 --- a/front_end/openVRE/public/api/.htaccess +++ b/front_end/openVRE/public/api/.htaccess @@ -2,4 +2,5 @@ RewriteEngine On RewriteCond %{REQUEST_URI} ^/api RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule ^ index.php [QSA,L] \ No newline at end of file +RewriteRule ^(.*)$ index.php [QSA,L] +CGIPassAuth On # https://serverfault.com/questions/1094686/rewriterule-e-http-authorizationhttpauthorization-what-does-it-mean#1094693 \ No newline at end of file diff --git a/front_end/openVRE/public/api/index.php b/front_end/openVRE/public/api/index.php index d8633bb7..24a3ec66 100644 --- a/front_end/openVRE/public/api/index.php +++ b/front_end/openVRE/public/api/index.php @@ -1,81 +1,209 @@ setBasePath("/api"); +$app->setBasePath("/api/v1"); $app->addErrorMiddleware(true, true, true); +function getBearerToken($authHeader) +{ + if (empty($authHeader)) { + throw new Exception('Authorization header not found'); + } + + $matchedBearer = preg_match('/^Bearer\s(\S+)$/', $authHeader, $bearerText); + if ($matchedBearer === 0) { + throw new Exception('Bearer authorization header not found'); + } + + if ($matchedBearer === false) { + throw new Exception('Error parsing authorization header'); + } + + return $bearerText[1]; +} + + +function validateToken(string $token) +{ + $jwksFilePath = '.jwks'; // Obtained from JWKS Endpoint of auth server + try { + $jwks = json_decode(file_get_contents($jwksFilePath), true); + $parsedKeySet = JWK::parseKeySet($jwks); + return JWT::decode($token, $parsedKeySet); + } catch (Exception $e) { + throw new Exception("Invalid token: " . $e->getMessage()); + } +} + + +function checkAuthorizationToken($request, $response) +{ + try { + $token = getBearerToken($request->getHeaderLine('Authorization')); + } catch (Exception $e) { + $response + ->getBody() + ->write(json_encode(['error' => $e->getMessage()])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(401); + } + + try { + validateToken($token); + } catch (Exception $e) { + $response + ->getBody() + ->write(json_encode(['error' => 'Forbidden: ' . $e->getMessage()])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(403); + } + + return $response; +} + + $app->get('/', function (Request $request, Response $response, $args) { $response->getBody()->write("Welcome to the OpenVRE API!\n"); return $response; }); -$app->get('/tools/', function (Request $request, Response $response, $args) { - if(!checkLoggedIn()){ - return $response->withStatus(401); +$app->get('/tools', function (Request $request, Response $response, $args) { + $response = checkAuthorizationToken($request, $response); + if ($response->getStatusCode() !== 200) { + return $response; } $tools = $GLOBALS['toolsCol']->find(); $payload = json_encode(iterator_to_array($tools)); $response->getBody()->write($payload); - return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(200); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(200); }); -$app->post('/jobs/', function (Request $request, Response $response, $args) { - $queryParams = $request->getQueryParams(); - if ($queryParams['tool'] != "mock_tool") { - $response->getBody()->write(json_encode(["Error" => ["You should provide a valid tool\n"]])); - return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(400); +$app->get('/tools/{id}', function (Request $request, Response $response, $args) { + $response = checkAuthorizationToken($request, $response); + if ($response->getStatusCode() !== 200) { + return $response; } - if (!filter_var($queryParams['email'], FILTER_VALIDATE_EMAIL)) { - $response->getBody()->write(json_encode(["Error" => ["You should provide a valid email\n"]])); + $options = array('typemap' => ['root' => 'array', 'document' => 'array']); + $tool = $GLOBALS['toolsCol']->findOne(array('_id' => $args['id']), $options); + if (empty($tool)) { + $response->getBody()->write(json_encode(["Error" => "Tool not found"])); return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(400); + ->withHeader('Content-Type', 'application/json') + ->withStatus(404); } + $payload = json_encode($tool); + $response->getBody()->write($payload); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(200); +}); + + +$app->post('/jobs', function (Request $request, Response $response, $args) { try { - $toolJson = launchTool($queryParams['tool'], $queryParams['email'], $queryParams['project'], $queryParams['input_files']); - } catch (MongoDB\Exception\Exception | MongoDB\Driver\Exception\Exception $e) { - $response->getBody()->write(json_encode(["Error" => "Error connecting to database: " . $e->getMessage()])); + $token = getBearerToken($request->getHeaderLine('Authorization')); + } catch (Exception $e) { + $response + ->getBody() + ->write(json_encode(['error' => $e->getMessage()])); + return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(500); + ->withHeader('Content-Type', 'application/json') + ->withStatus(401); + } + + try { + $decodedToken = validateToken($token); + $userEmail = $decodedToken->email; } catch (Exception $e) { - $response->getBody()->write(json_encode(["Error" => $e->getMessage()])); + $response + ->getBody() + ->write(json_encode(['error' => 'Forbidden: ' . $e->getMessage()])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(403); + } + + $queryParams = $request->getQueryParams(); + if (empty($queryParams['tool'])) { + $response->getBody()->write(json_encode(["Error" => "Missing tool id"])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(400); + } + + if (empty($queryParams['project'])) { + $response->getBody()->write(json_encode(["Error" => "Missing project id"])); + return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(500); + ->withHeader('Content-Type', 'application/json') + ->withStatus(400); } - if ($_SESSION['errorData'] != null) { - $response->getBody()->write(json_encode(["Error" => $_SESSION['errorData']['Error']])); // Not returning warnings or other type of error data + $parsedBody = $request->getParsedBody(); + + if (empty($parsedBody['input_files'])) { + $response->getBody()->write(json_encode(["Error" => "Missing input files"])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(400); + } + + try { + $toolJson = launchTool($queryParams['tool'], $userEmail, $queryParams['project'], $parsedBody['input_files']); + } catch (Exception $e) { + $response->getBody()->write(json_encode(["Error" => "Error connecting to database: " . $e->getMessage()])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(500); + } + + if ($_SESSION['errorData']['Error'] != null) { + $response->getBody()->write(json_encode(["Error" => $_SESSION['errorData']['Error']])); + $_SESSION['errorData']['Error'] = null; // Clear the error data from the session to avoid concatenation on futher requests + return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(500); + ->withHeader('Content-Type', 'application/json') + ->withStatus(500); } $payload = json_encode($toolJson); $response->getBody()->write($payload); return $response - ->withHeader('Content-Type', 'application/json') - ->withStatus(200); + ->withHeader('Content-Type', 'application/json') + ->withStatus(200); }); diff --git a/front_end/openVRE/public/api/launchTool.php b/front_end/openVRE/public/api/launchTool.php index 054b4e6a..4b42f75a 100644 --- a/front_end/openVRE/public/api/launchTool.php +++ b/front_end/openVRE/public/api/launchTool.php @@ -1,9 +1,10 @@ count($inputFilesKeys)) { + $_SESSION['errorData']['Error'][] = "Too many files given. Tool has " . count($inputFilesKeys) . " input files at most."; + return 0; + } - $inputFileId = getGSFileId_fromPath($inputFilePath); - $input_files[$pathParts['filename']][] = $inputFileId; + for ($inputFileIndex = 0; $inputFileIndex < count($inputFilepaths); $inputFileIndex++) { + $inputFileFullPath = $projectDirPath . "/uploads/" . $inputFilepaths[$inputFileIndex]; // Assuming input files are in uploads folder + $inputFileId = getGSFileId_fromPath($inputFileFullPath); + if (empty($inputFileId)) { + $_SESSION['errorData']['Error'][] = "Input file '" . $inputFilepaths[$inputFileIndex] . "' does not exist or does not belong to current user"; + return 0; + } + + $inputFileGenericName = $tool['input_files'][$inputFilesKeys[$inputFileIndex]]['name']; + $input_files[$inputFileGenericName][] = $inputFileId; + } $jobMeta = new Tooljob($tool, $executionName, $projectDir, $description); @@ -44,38 +58,39 @@ function launchTool($toolId, $userEmail, $projectName, $inputFiles) { $filesId = []; foreach ($input_files as $input_file) { if (is_array($input_file)) { - $filesId = array_merge($filesId,$input_file); + $filesId = array_merge($filesId, $input_file); } else { if ($input_file) { - array_push($filesId,$input_file); + array_push($filesId, $input_file); } } } $filesId = array_unique($filesId); - foreach ($filesId as $fnId){ + foreach ($filesId as $fnId) { $file = getGSFile_fromId($fnId); - - if (!$file){ + + if (!$file) { continue; } - $files[$file['_id']]=$file; - + $files[$file['_id']] = $file; + $associated_files = getAssociatedFiles_fromId($fnId); - foreach ($associated_files as $assocId){ + foreach ($associated_files as $assocId) { $assocFile = getGSFile_fromId($assocId); - if (!$assocFile){ - $_SESSION['errorData']['Error'][]="File associated to ".basename($file['path'])." ($assocId) does not belong to current user or has been not properly registered. Stopping execution"; - redirect($GLOBALS['BASEURL']."workspace/"); + if (!$assocFile) { + $_SESSION['errorData']['Error'][] = "File associated to " . basename($file['path']) . " ($assocId) does not belong to current user or has been not properly registered. Stopping execution"; + redirect($GLOBALS['BASEURL'] . "workspace/"); } - $files[$assocFile['_id']]=$assocFile; + $files[$assocFile['_id']] = $assocFile; } } $jobMeta->setArguments($arguments, $tool); $r = $jobMeta->setInput_files($input_files, $tool, $files); if ($r == "0") { - return ["Error setting input files"]; + $_SESSION['errorData']['Error'][] = ["Error setting input files."]; + return 0; } $input_files_public = []; @@ -92,7 +107,7 @@ function launchTool($toolId, $userEmail, $projectName, $inputFiles) { return 0; } - $r = $jobMeta->prepareExecution($tool,$files,$files_pub); + $r = $jobMeta->prepareExecution($tool, $files, $files_pub); if ($r == 0) { $_SESSION['errorData']['Error'][] = "Error preparing execution."; return 0; @@ -104,9 +119,7 @@ function launchTool($toolId, $userEmail, $projectName, $inputFiles) { return 0; } - addUserJob($_SESSION['User']['_id'],(array)$jobMeta,$jobMeta->pid); - $jobInfoToUser = ["jobTitle" => $jobMeta->title, "project" => $projectName, "execution" => $jobMeta->execution]; + addUserJob($_SESSION['User']['_id'], (array)$jobMeta, $jobMeta->pid); - return $jobInfoToUser; + return ["jobTitle" => $jobMeta->title, "project" => $projectName, "execution" => $jobMeta->execution]; } -