FOSSology  4.4.0
Open Source License Compliance by Open Source Software
index.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2017-2018,2021 Siemens AG
4  SPDX-FileCopyrightText: © 2021 Orange by Piotr Pszczola <piotr.pszczola@orange.com>
5  SPDX-FileCopyrightText: © 2023 Samuel Dushimimana <dushsam100@gmail.com>
6 
7  SPDX-License-Identifier: GPL-2.0-only
8 */
15 namespace Fossology\UI\Api;
16 
17 $GLOBALS['apiCall'] = true;
18 
19 // setup autoloading
20 require_once dirname(__DIR__, 3) . "/vendor/autoload.php";
21 require_once dirname(__FILE__, 4) . "/lib/php/bootstrap.php";
22 
53 use Psr\Http\Message\ServerRequestInterface;
54 use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
55 use Psr\Log\LoggerInterface;
56 use Slim\Exception\HttpMethodNotAllowedException;
57 use Slim\Exception\HttpNotFoundException;
58 use Slim\Factory\AppFactory;
59 use Slim\Middleware\ContentLengthMiddleware;
60 use Slim\Psr7\Request;
61 use Slim\Psr7\Response;
62 use Throwable;
63 
64 // Extracts the version from the URL
65 function getVersionFromUri ($uri)
66 {
67  $matches = [];
68  preg_match('/\/repo\/api\/v(\d+)/', $uri, $matches);
69  return isset($matches[1]) ? intval($matches[1]) : null;
70 }
71 
72 // Determine the API version based on the URL
73 $requestedVersion = isset($_SERVER['REQUEST_URI']) ? getVersionFromUri($_SERVER['REQUEST_URI']) : null;
74 $apiVersion = in_array($requestedVersion, [ApiVersion::V1, ApiVersion::V2]) ? $requestedVersion : ApiVersion::V1; // Default to "1"
75 
76 // Construct the base path
77 $BASE_PATH = "/repo/api/v" .$apiVersion;
78 
79 const AUTH_METHOD = "JWT_TOKEN";
80 
81 $GLOBALS['apiBasePath'] = $BASE_PATH;
82 
83 $startTime = microtime(true);
84 
85 /* Set SYSCONFDIR and set global (for backward compatibility) */
86 $SysConf = bootstrap();
87 
88 global $container;
90 $timingLogger = $container->get("log.timing");
91 $timingLogger->logWithStartTime("bootstrap", $startTime);
92 
93 /* Load UI templates */
94 $loader = $container->get('twig.loader');
95 $loader->addPath(dirname(__FILE__, 2) .'/template');
96 
97 /* Initialize global system configuration variables $SysConfig[] */
98 $timingLogger->tic();
99 $error = ConfigInit($GLOBALS['SYSCONFDIR'], $SysConf, false);
100 
101 $dbConnected = true;
102 if ($error === -1) {
103  $dbConnected = false;
104 }
105 
106 $timingLogger->toc("setup init");
107 
108 $timingLogger->tic();
109 if ($dbConnected) {
110  plugin_load();
111 }
112 
113 AppFactory::setContainer($container);
114 AppFactory::setResponseFactory(new ResponseFactoryHelper());
115 $app = AppFactory::create();
116 $app->setBasePath($BASE_PATH);
117 
118 // Custom middleware to set the API version as a request attribute
119 $apiVersionMiddleware = function (Request $request, RequestHandler $handler) use ($apiVersion) {
120  $request = $request->withAttribute(ApiVersion::ATTRIBUTE_NAME, $apiVersion);
121  return $handler->handle($request);
122 };
123 
124 /*
125  * To check the order of middlewares, refer
126  * https://www.slimframework.com/docs/v4/concepts/middleware.html
127  *
128  * FOSSology Init is the first middleware and Rest Auth is second.
129  *
130  * 1. The call enters from Rest Auth and initialize session variables.
131  * 2. It then goes to FOSSology Init and initialize all plugins
132  * 3. Added ApiVersion middleware to set 'apiVersion' attribute in request.
133  * 4. The normal flow continues.
134  * 5. The call enters ApiVersion middleware and leaves as is.
135  * 6. The call now enters FOSSology Init again and plugins are unloaded.
136  * 7. The call then enters Rest Auth and leaves as is.
137  */
138 if ($dbConnected) {
139  // Middleware for plugin initialization
140  $app->add(new FossologyInitMiddleware());
141  // Middleware for authentication
142  $app->add(new RestAuthMiddleware());
143  // Content length middleware
144  $app->add(new ContentLengthMiddleware());
145  // Api version middleware
146  $app->add($apiVersionMiddleware);
147 } else {
148  // DB not connected
149  // Respond to health request as expected
150  $app->get('/health', function($req, $res) {
151  $handler = new InfoController($GLOBALS['container']);
152  return $handler->getHealth($req, $res, -1);
153  });
154  // Handle any other request and respond explicitly
155  $app->any('{route:.*}', function(ServerRequestInterface $req, ResponseHelper $res) {
156  $error = new Info(503, "Unable to connect to DB.", InfoType::ERROR);
157  return $res->withJson($error->getArray(), $error->getCode());
158  });
159 
160  // Prevent further actions and exit
161  $app->run();
162  return 0;
163 }
164 
166 $app->options('/{routes:.+}', AuthController::class . ':optionsVerification');
167 
169 $app->post('/tokens', AuthController::class . ':createNewJwtToken');
170 
172 $app->group('/uploads',
173  function (\Slim\Routing\RouteCollectorProxy $app) {
174  $app->get('[/{id:\\d+}]', UploadController::class . ':getUploads');
175  $app->delete('/{id:\\d+}', UploadController::class . ':deleteUpload');
176  $app->patch('/{id:\\d+}', UploadController::class . ':updateUpload');
177  $app->put('/{id:\\d+}', UploadController::class . ':moveUpload');
178  $app->post('', UploadController::class . ':postUpload');
179  $app->put('/{id:\\d+}/permissions', UploadController::class . ':setUploadPermissions');
180  $app->get('/{id:\\d+}/perm-groups', UploadController::class . ':getGroupsWithPermissions');
181  $app->get('/{id:\\d+}/groups/permission', UploadController::class . ':getGroupsWithPermissions');
182  $app->get('/{id:\\d+}/summary', UploadController::class . ':getUploadSummary');
183  $app->get('/{id:\\d+}/agents', UploadController::class . ':getAllAgents');
184  $app->get('/{id:\\d+}/agents/revision', UploadController::class . ':getAgentsRevision');
185  $app->get('/{id:\\d+}/licenses', UploadController::class . ':getUploadLicenses');
186  $app->get('/{id:\\d+}/licenses/histogram', UploadController::class . ':getLicensesHistogram');
187  $app->get('/{id:\\d+}/licenses/edited', UploadController::class . ':getEditedLicenses');
188  $app->get('/{id:\\d+}/licenses/reuse', UploadController::class . ':getReuseReportSummary');
189  $app->get('/{id:\\d+}/licenses/scanned', UploadController::class . ':getScannedLicenses');
190  $app->get('/{id:\\d+}/licenses/main', UploadController::class . ':getMainLicenses');
191  $app->post('/{id:\\d+}/licenses/main', UploadController::class . ':setMainLicense');
192  $app->get('/{id:\\d+}/download', UploadController::class . ':uploadDownload');
193  $app->get('/{id:\\d+}/clearing-progress', UploadController::class . ':getClearingProgressInfo');
194  $app->delete('/{id:\\d+}/licenses/{shortName:[\\w\\- \\.]+}/main', UploadController::class . ':removeMainLicense');
195  $app->get('/{id:\\d+}/topitem', UploadController::class . ':getTopItem');
196  $app->put('/{id:\\d+}/item/{itemId:\\d+}/licenses', UploadTreeController::class . ':handleAddEditAndDeleteLicenseDecision');
197  $app->get('/{id:\\d+}/item/{itemId:\\d+}/view', UploadTreeController::class. ':viewLicenseFile');
198  $app->get('/{id:\\d+}/item/{itemId:\\d+}/prev-next', UploadTreeController::class . ':getNextPreviousItem');
199  $app->get('/{id:\\d+}/item/{itemId:\\d+}/licenses', UploadTreeController::class . ':getLicenseDecisions');
200  $app->put('/{id:\\d+}/item/{itemId:\\d+}/clearing-decision', UploadTreeController::class . ':setClearingDecision');
201  $app->get('/{id:\\d+}/item/{itemId:\\d+}/bulk-history', UploadTreeController::class . ':getBulkHistory');
202  $app->get('/{id:\\d+}/item/{itemId:\\d+}/clearing-history', UploadTreeController::class . ':getClearingHistory');
203  $app->get('/{id:\\d+}/item/{itemId:\\d+}/highlight', UploadTreeController::class . ':getHighlightEntries');
204  $app->get('/{id:\\d+}/item/{itemId:\\d+}/totalcopyrights', CopyrightController::class . ':getTotalFileCopyrights');
205  $app->get('/{id:\\d+}/item/{itemId:\\d+}/tree/view', UploadTreeController::class . ':getTreeView');
206  $app->get('/{id:\\d+}/item/{itemId:\\d+}/info', FileInfoController::class . ':getItemInfo');
207  $app->post('/{id:\\d+}/item/{itemId:\\d+}/bulk-scan', UploadTreeController::class . ':scheduleBulkScan');
208  $app->get('/{id:\\d+}/conf', ConfController::class . ':getConfInfo');
209  $app->put('/{id:\\d+}/conf', ConfController::class . ':updateConfData');
210  $app->get('/{id:\\d+}/copyrights', UploadController::class . ':getUploadCopyrights');
212  $app->group('/{id:\\d+}/item/{itemId:\\d+}', function (\Slim\Routing\RouteCollectorProxy $app) {
213  $app->get('/copyrights', CopyrightController::class . ':getFileCopyrights');
214  $app->delete('/copyrights/{hash:.*}', CopyrightController::class . ':deleteFileCopyright');
215  $app->patch('/copyrights/{hash:.*}', CopyrightController::class . ':restoreFileCopyright');
216  $app->put('/copyrights/{hash:.*}', CopyrightController::class . ':updateFileCopyright');
217  $app->get('/emails', CopyrightController::class . ':getFileEmail');
218  $app->delete('/emails/{hash:.*}', CopyrightController::class . ':deleteFileEmail');
219  $app->patch('/emails/{hash:.*}', CopyrightController::class . ':restoreFileEmail');
220  $app->put('/emails/{hash:.*}', CopyrightController::class . ':updateFileEmail');
221  $app->get('/urls', CopyrightController::class . ':getFileUrl');
222  $app->delete('/urls/{hash:.*}', CopyrightController::class . ':deleteFileUrl');
223  $app->patch('/urls/{hash:.*}', CopyrightController::class . ':restoreFileUrl');
224  $app->put('/urls/{hash:.*}', CopyrightController::class . ':updateFileUrl');
225  $app->get('/authors', CopyrightController::class . ':getFileAuthor');
226  $app->delete('/authors/{hash:.*}', CopyrightController::class . ':deleteFileAuthor');
227  $app->patch('/authors/{hash:.*}', CopyrightController::class . ':restoreFileAuthor');
228  $app->put('/authors/{hash:.*}', CopyrightController::class . ':updateFileAuthor');
229  $app->get('/eccs', CopyrightController::class . ':getFileEcc');
230  $app->delete('/eccs/{hash:.*}', CopyrightController::class . ':deleteFileEcc');
231  $app->patch('/eccs/{hash:.*}', CopyrightController::class . ':restoreFileEcc');
232  $app->put('/eccs/{hash:.*}', CopyrightController::class . ':updateFileEcc');
233  $app->get('/keywords', CopyrightController::class . ':getFileKeyword');
234  $app->delete('/keywords/{hash:.*}', CopyrightController::class . ':deleteFileKeyword');
235  $app->patch('/keywords/{hash:.*}', CopyrightController::class . ':restoreFileKeyword');
236  $app->put('/keywords/{hash:.*}', CopyrightController::class . ':updateFileKeyword');
237  $app->get('/ipras', CopyrightController::class . ':getFileIpra');
238  $app->delete('/ipras/{hash:.*}', CopyrightController::class . ':deleteFileIpra');
239  $app->patch('/ipras/{hash:.*}', CopyrightController::class . ':restoreFileIpra');
240  $app->put('/ipras/{hash:.*}', CopyrightController::class . ':updateFileIpra');
241  });
242  $app->any('/{params:.*}', BadRequestController::class);
243  });
244 
245 
247 $app->group('/users',
248  function (\Slim\Routing\RouteCollectorProxy $app) {
249  $app->get('[/{id:\\d+}]', UserController::class . ':getUsers');
250  $app->put('[/{id:\\d+}]', UserController::class . ':updateUser');
251  $app->post('', UserController::class . ':addUser');
252  $app->delete('/{id:\\d+}', UserController::class . ':deleteUser');
253  $app->get('/self', UserController::class . ':getCurrentUser');
254  $app->post('/tokens', UserController::class . ':createRestApiToken');
255  $app->get('/tokens/{type:\\w+}', UserController::class . ':getTokens');
256  $app->any('/{params:.*}', BadRequestController::class);
257  });
258 
260 $app->group('/obligations',
261  function (\Slim\Routing\RouteCollectorProxy $app) {
262  $app->get('/list', ObligationController::class . ':obligationsList');
263  $app->get('/{id:\\d+}', ObligationController::class . ':obligationsDetails');
264  $app->get('', ObligationController::class . ':obligationsAllDetails');
265  $app->delete('/{id:\\d+}', ObligationController::class . ':deleteObligation');
266  $app->get('/export-csv', ObligationController::class . ':exportObligationsToCSV');
267  $app->post('/import-csv', ObligationController::class . ':importObligationsFromCSV');
268  $app->any('/{params:.*}', BadRequestController::class);
269  });
270 
272 $app->group('/groups',
273  function (\Slim\Routing\RouteCollectorProxy $app) {
274  $app->get('', GroupController::class . ':getGroups');
275  $app->post('', GroupController::class . ':createGroup');
276  $app->post('/{id:\\d+}/user/{userId:\\d+}', GroupController::class . ':addMember');
277  $app->delete('/{id:\\d+}', GroupController::class . ':deleteGroup');
278  $app->delete('/{id:\\d+}/user/{userId:\\d+}', GroupController::class . ':deleteGroupMember');
279  $app->get('/deletable', GroupController::class . ':getDeletableGroups');
280  $app->get('/{id:\\d+}/members', GroupController::class . ':getGroupMembers');
281  $app->put('/{id:\\d+}/user/{userId:\\d+}', GroupController::class . ':changeUserPermission');
282  $app->any('/{params:.*}', BadRequestController::class);
283  });
284 
286 $app->group('/jobs',
287  function (\Slim\Routing\RouteCollectorProxy $app) {
288  $app->get('[/{id:\\d+}]', JobController::class . ':getJobs');
289  $app->get('/all', JobController::class . ':getAllJobs');
290  $app->get('/dashboard/statistics', JobController::class . ':getJobStatistics');
291  $app->get('/scheduler/operation/{operationName:[\\w\\- \\.]+}', JobController::class . ':getSchedulerJobOptionsByOperation');
292  $app->post('/scheduler/operation/run', JobController::class . ':handleRunSchedulerOption');
293  $app->post('', JobController::class . ':createJob');
294  $app->get('/history', JobController::class . ':getJobsHistory');
295  $app->get('/dashboard', JobController::class . ':getAllServerJobsStatus');
296  $app->delete('/{id:\\d+}/{queue:\\d+}', JobController::class . ':deleteJob');
297  $app->any('/{params:.*}', BadRequestController::class);
298  });
299 
301 $app->group('/search',
302  function (\Slim\Routing\RouteCollectorProxy $app) {
303  $app->get('', SearchController::class . ':performSearch');
304  });
305 
307 $app->group('/maintenance',
308  function (\Slim\Routing\RouteCollectorProxy $app) {
309  $app->post('', MaintenanceController::class . ':createMaintenance');
310  $app->any('/{params:.*}', BadRequestController::class);
311  });
312 
313 
315 $app->group('/folders',
316  function (\Slim\Routing\RouteCollectorProxy $app) {
317  $app->get('[/{id:\\d+}]', FolderController::class . ':getFolders');
318  $app->post('', FolderController::class . ':createFolder');
319  $app->delete('/{id:\\d+}', FolderController::class . ':deleteFolder');
320  $app->patch('/{id:\\d+}', FolderController::class . ':editFolder');
321  $app->put('/{id:\\d+}', FolderController::class . ':copyFolder');
322  $app->get('/{id:\\d+}/contents/unlinkable', FolderController::class . ':getUnlinkableFolderContents');
323  $app->put('/contents/{contentId:\\d+}/unlink', FolderController::class . ':unlinkFolder');
324  $app->get('/{id:\\d+}/contents', FolderController::class . ':getAllFolderContents');
325  $app->any('/{params:.*}', BadRequestController::class);
326  });
327 
329 $app->group('/report',
330  function (\Slim\Routing\RouteCollectorProxy $app) {
331  $app->get('', ReportController::class . ':getReport');
332  $app->get('/{id:\\d+}', ReportController::class . ':downloadReport');
333  $app->post('/import', ReportController::class . ':importReport');
334  $app->any('/{params:.*}', BadRequestController::class);
335  });
336 
338 $app->group('/customise',
339  function (\Slim\Routing\RouteCollectorProxy $app) {
340  $app->get('', CustomiseController::class . ':getCustomiseData');
341  $app->put('', CustomiseController::class . ':updateCustomiseData');
342  $app->get('/banner', CustomiseController::class . ':getBannerMessage');
343  $app->any('/{params:.*}', BadRequestController::class);
344  });
345 
347 $app->group('/info',
348  function (\Slim\Routing\RouteCollectorProxy $app) {
349  $app->get('', InfoController::class . ':getInfo');
350  });
351 $app->group('/health',
352  function (\Slim\Routing\RouteCollectorProxy $app) {
353  $app->get('', InfoController::class . ':getHealth');
354  });
355 $app->group('/openapi',
356  function (\Slim\Routing\RouteCollectorProxy $app) {
357  $app->get('', InfoController::class . ':getOpenApi');
358  });
359 
361 $app->group('/filesearch',
362  function (\Slim\Routing\RouteCollectorProxy $app) {
363  $app->post('', FileSearchController::class . ':getFiles');
364  $app->any('/{params:.*}', BadRequestController::class);
365  });
366 
368 $app->group('/license',
369  function (\Slim\Routing\RouteCollectorProxy $app) {
370  $app->get('', LicenseController::class . ':getAllLicenses');
371  $app->post('/import-csv', LicenseController::class . ':handleImportLicense');
372  $app->get('/export-csv', LicenseController::class . ':exportAdminLicenseToCSV');
373  $app->post('', LicenseController::class . ':createLicense');
374  $app->put('/verify/{shortname:.+}', LicenseController::class . ':verifyLicense');
375  $app->put('/merge/{shortname:.+}', LicenseController::class . ':mergeLicense');
376  $app->get('/admincandidates', LicenseController::class . ':getCandidates');
377  $app->get('/adminacknowledgements', LicenseController::class . ':getAllAdminAcknowledgements');
378  $app->get('/stdcomments', LicenseController::class . ':getAllLicenseStandardComments');
379  $app->put('/stdcomments', LicenseController::class . ':handleLicenseStandardComment');
380  $app->post('/suggest', LicenseController::class . ':getSuggestedLicense');
381  $app->get('/{shortname:.+}', LicenseController::class . ':getLicense');
382  $app->patch('/{shortname:.+}', LicenseController::class . ':updateLicense');
383  $app->delete('/admincandidates/{id:\\d+}',
384  LicenseController::class . ':deleteAdminLicenseCandidate');
385  $app->put('/adminacknowledgements', LicenseController::class . ':handleAdminLicenseAcknowledgement');
386  $app->any('/{params:.*}', BadRequestController::class);
387  });
388 
390 $app->group('/overview',
391  function (\Slim\Routing\RouteCollectorProxy $app) {
392  $app->get('/database/contents', OverviewController::class . ':getDatabaseContents');
393  $app->get('/disk/usage', OverviewController::class . ':getDiskSpaceUsage');
394  $app->get('/info/php', OverviewController::class . ':getPhpInfo');
395  $app->get('/database/metrics', OverviewController::class . ':getDatabaseMetrics');
396  $app->get('/queries/active', OverviewController::class . ':getActiveQueries');
397  $app->any('/{params:.*}', BadRequestController::class);
398  });
399 
401 // Define Custom Error Handler
402 $customErrorHandler = function (
403  ServerRequestInterface $request,
404  Throwable $exception,
405  bool $displayErrorDetails,
406  bool $logErrors,
407  bool $logErrorDetails,
408  ?LoggerInterface $logger = null
409 ) use ($app) {
410  if ($logger === null) {
411  $logger = $app->getContainer()->get('logger');
412  }
413  if ($logErrors) {
414  $logger->error($exception->getMessage(), $exception->getTrace());
415  }
416  if ($displayErrorDetails) {
417  $payload = ['error'=> $exception->getMessage(),
418  'trace' => $exception->getTraceAsString()];
419  } else {
420  $error = new Info(500, "Something went wrong! Please try again later.",
421  InfoType::ERROR);
422  $payload = $error->getArray();
423  }
424 
425  $response = $app->getResponseFactory()->createResponse(500)
426  ->withHeader("Content-Type", "application/json");
427  $response->getBody()->write(
428  json_encode($payload, JSON_UNESCAPED_UNICODE)
429  );
430 
431  plugin_unload();
432  return CorsHelper::addCorsHeaders($response);
433 };
434 
435 $errorMiddleware = $app->addErrorMiddleware(false, true, true,
436  $container->get("logger"));
437 
438 // Catch all routes
439 $errorMiddleware->setErrorHandler(
440  HttpNotFoundException::class,
441  function (ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails) {
442  $response = new ResponseHelper();
443  $error = new Info(404, "Resource not found", InfoType::ERROR);
444  $response = $response->withJson($error->getArray(), $error->getCode());
445  plugin_unload();
446  return CorsHelper::addCorsHeaders($response);
447  });
448 
449 // Set the Not Allowed Handler
450 $errorMiddleware->setErrorHandler(
451  HttpMethodNotAllowedException::class,
452  function (ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails) {
453  $response = new Response();
454  $response->getBody()->write('405 NOT ALLOWED');
455 
456  $response = $response->withStatus(405);
457  plugin_unload();
458  return CorsHelper::addCorsHeaders($response);
459  });
460 
461 // Set custom error handler
462 $errorMiddleware->setErrorHandler(
463  HttpErrorException::class,
464  function (ServerRequestInterface $request, HttpErrorException $exception, bool $displayErrorDetails) {
465  $response = new ResponseHelper();
466  $error = new Info($exception->getCode(), $exception->getMessage(),
467  InfoType::ERROR);
468  $response = $response->withJson($error->getArray(), $error->getCode());
469  if (!empty($exception->getHeaders())) {
470  foreach ($exception->getHeaders() as $key => $value) {
471  $response = $response->withHeader($key, $value);
472  }
473  }
474  plugin_unload();
475  return CorsHelper::addCorsHeaders($response);
476  }, true
477 );
478 
479 $errorMiddleware->setDefaultErrorHandler($customErrorHandler);
480 
481 $app->run();
482 
483 $GLOBALS['container']->get("db.manager")->flushStats();
484 return 0;
Controller for REST API version.
Controller for OverviewController model.
static addCorsHeaders(ResponseInterface $response)
Definition: CorsHelper.php:21
Override Slim response factory for custom response.
Override Slim response for withJson function.
Middleware to initialize FOSSology for Slim framework.
Authentication middleware for Slim framework.
Different type of infos provided by REST.
Definition: InfoType.php:16
Info model to contain general error and return values.
Definition: Info.php:19
plugin_load()
Load every module ui found in mods-enabled.
ConfigInit($sysconfdir, &$SysConf, $exitOnDbFail=true)
Initialize the fossology system after bootstrap().
bootstrap($sysconfdir="")
Bootstrap the fossology php library.
Definition: migratetest.php:82