FOSSology  4.6.0-rc1
Open Source License Compliance by Open Source Software
OsselotController.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2025 Vaibhav Sahu <sahusv4527@gmail.com>
4  SPDX-License-Identifier: GPL-2.0-only
5 */
6 
7 namespace Fossology\UI\Api\Controllers;
8 
18 use Symfony\Component\HttpFoundation\Request;
19 
25 {
34  public function getPackageVersions($request, $response, $args): ResponseHelper
35  {
36  global $SysConf;
37 
38  if (empty($SysConf['SYSCONFIG']['EnableOsselotReuse']) ||
39  !$SysConf['SYSCONFIG']['EnableOsselotReuse']) {
40  $error = new Info(501, "OSSelot integration is disabled", InfoType::ERROR);
41  return $response->withJson($error->getArray(), $error->getCode());
42  }
43 
44  $package = $args['package'] ?? "";
45  if (strlen(trim($package)) == 0) {
46  throw new HttpBadRequestException("Missing package name");
47  }
48 
49  try {
50  $helper = new OsselotLookupHelper();
51  $versions = $helper->getVersions($package);
52 
53  if (empty($versions)) {
54  $error = new Info(404, "No curated versions found for '$package'", InfoType::ERROR);
55  return $response->withJson($error->getArray(), $error->getCode());
56  }
57 
58  rsort($versions, SORT_NATURAL);
59 
60  return $response->withJson($versions, 200);
61 
62  } catch (\Exception $e) {
63  $error = new Info(502, "OSSelot unavailable, try later", InfoType::ERROR);
64  return $response->withJson($error->getArray(), $error->getCode());
65  }
66  }
67 
76  public function importOsselotReport($request, $response, $args): ResponseHelper
77  {
78  global $SysConf;
79 
80  if (empty($SysConf['SYSCONFIG']['EnableOsselotReuse']) ||
81  !$SysConf['SYSCONFIG']['EnableOsselotReuse']) {
82  $err = new Info(501, "OSSelot integration is disabled", InfoType::ERROR);
83  return $response->withJson($err->getArray(), $err->getCode());
84  }
85 
86  $uploadId = (int)($args['id'] ?? 0);
87 
88  if (!$this->dbHelper->doesIdExist('upload', 'upload_pk', $uploadId)) {
89  $err = new Info(404, "Upload does not exist", InfoType::ERROR);
90  return $response->withJson($err->getArray(), $err->getCode());
91  }
92 
93  $body = $this->getParsedBody($request);
94  $pkg = $body['package'] ?? null;
95  $ver = $body['version'] ?? null;
96  $options = $body['options'] ?? null;
97 
98  if (!$pkg || !$ver || !is_array($options)) {
99  throw new HttpBadRequestException("Missing required fields: package, version, options");
100  }
101  $requiredOptions = [
102  'addLicenseInfoFromInfoInFile',
103  'addLicenseInfoFromConcluded',
104  'addConcludedAsDecisions',
105  'addCopyrights'
106  ];
107 
108  foreach ($requiredOptions as $optKey) {
109  if (!array_key_exists($optKey, $options)) {
110  throw new HttpBadRequestException("Option '$optKey' is required");
111  }
112  }
113 
114  $jobDao = $this->restHelper->getJobDao();
115  if (method_exists($jobDao, 'hasPendingReportImport') &&
116  $jobDao->hasPendingReportImport($uploadId)) {
117  $err = new Info(409, "An import job is already in progress for this upload", InfoType::ERROR);
118  return $response->withJson($err->getArray(), $err->getCode());
119  }
120 
121  try {
122  $helper = new OsselotLookupHelper();
123  $cachedPath = $helper->fetchSpdxFile($pkg, $ver);
124 
125  if (!$cachedPath || !is_file($cachedPath) || !is_readable($cachedPath)) {
126  $err = new Info(404, "No curated SPDX report found for '$pkg' version '$ver'", InfoType::ERROR);
127  return $response->withJson($err->getArray(), $err->getCode());
128  }
129 
130  $fileBase = $SysConf['FOSSOLOGY']['path'] . "/ReportImport/";
131  if (!is_dir($fileBase) && !mkdir($fileBase, 0755, true)) {
132  throw new \RuntimeException('Failed to create ReportImport directory');
133  }
134 
135  $originalName = basename($cachedPath);
136  if (!str_ends_with($originalName, '.rdf.xml')) {
137  $baseName = pathinfo($originalName, PATHINFO_FILENAME);
138  $originalName = str_ends_with($originalName, '.rdf') ?
139  $baseName . '.rdf.xml' : $originalName . '.rdf.xml';
140  }
141 
142  $targetFile = time() . '_' . random_int(0, getrandmax()) . '_osselot_' . $originalName;
143  $targetPath = $fileBase . $targetFile;
144 
145  if (!copy($cachedPath, $targetPath)) {
146  throw new \RuntimeException('Failed to copy SPDX file to target location');
147  }
148 
149  $reportImportAgent = plugin_find('agent_reportImport');
150  if (!$reportImportAgent || !method_exists($reportImportAgent, 'addReport') ||
151  !method_exists($reportImportAgent, 'setAdditionalJqCmdArgs') ||
152  !method_exists($reportImportAgent, 'AgentAdd')) {
153  throw new \RuntimeException('ReportImport agent not available or missing required methods');
154  }
155 
156  $importRequest = new Request();
157 
158  $addNewLicensesAs = $options['addNewLicensesAs'] ?? 'candidate';
159  if (!in_array($addNewLicensesAs, ['candidate', 'approved', 'rejected'], true)) {
160  $addNewLicensesAs = 'candidate';
161  }
162  $importRequest->request->set('addNewLicensesAs', $addNewLicensesAs);
163 
164  $booleanOptions = [
165  'addLicenseInfoFromInfoInFile' => $options['addLicenseInfoFromInfoInFile'],
166  'addLicenseInfoFromConcluded' => $options['addLicenseInfoFromConcluded'],
167  'addConcludedAsDecisions' => $options['addConcludedAsDecisions'],
168  'addConcludedAsDecisionsOverwrite' => $options['addConcludedAsDecisionsOverwrite'] ?? false,
169  'addConcludedAsDecisionsTBD' => $options['addConcludedAsDecisionsTBD'] ?? false,
170  'addCopyrights' => $options['addCopyrights']
171  ];
172 
173  foreach ($booleanOptions as $key => $value) {
174  $importRequest->request->set($key, $value ? 'true' : 'false');
175  }
176 
177  $licenseMatch = $options['licenseMatch'] ?? 'spdxid';
178  if (!in_array($licenseMatch, ['spdxid', 'name', 'text'], true)) {
179  $licenseMatch = 'spdxid';
180  }
181  $importRequest->request->set('licenseMatch', $licenseMatch);
182 
183  $jqCmdArgs = $reportImportAgent->addReport($targetFile);
184  $additionalArgs = $reportImportAgent->setAdditionalJqCmdArgs($importRequest);
185  $jqCmdArgs .= $additionalArgs;
186 
187  $userId = Auth::getUserId();
188  $groupId = Auth::getGroupId();
189 
190  $jobId = JobAddJob($userId, $groupId, "OSSelot Import", $uploadId);
191 
192  $error = "";
193  $dependencies = array();
194  $jobQueueId = $reportImportAgent->AgentAdd($jobId, $uploadId, $error, $dependencies, $jqCmdArgs);
195 
196  if ($jobQueueId < 0) {
197  if (file_exists($targetPath)) {
198  unlink($targetPath);
199  }
200  throw new \RuntimeException("Cannot schedule import job: " . $error);
201  }
202 
203  $info = new Info(202, "Import job scheduled successfully", InfoType::INFO);
204  $responseData = $info->getArray();
205  $responseData['jobId'] = intval($jobQueueId);
206 
207  return $response->withJson($responseData, $info->getCode());
208 
209  } catch (\InvalidArgumentException $e) {
210  $err = new Info(400, $e->getMessage(), InfoType::ERROR);
211  return $response->withJson($err->getArray(), $err->getCode());
212  } catch (\RuntimeException $e) {
213  if (isset($targetPath) && file_exists($targetPath)) {
214  unlink($targetPath);
215  }
216 
217  if (strpos($e->getMessage(), 'Could not fetch') !== false ||
218  strpos($e->getMessage(), 'No curated') !== false) {
219  $err = new Info(404, "No curated SPDX report found for '$pkg' version '$ver'", InfoType::ERROR);
220  } else {
221  $err = new Info(502, "OSSelot service unavailable, try later", InfoType::ERROR);
222  }
223  return $response->withJson($err->getArray(), $err->getCode());
224  } catch (\Exception $e) {
225  if (isset($targetPath) && file_exists($targetPath)) {
226  unlink($targetPath);
227  }
228 
229  error_log("OSSelot import error: " . $e->getMessage());
230  $err = new Info(502, "OSSelot service unavailable, try later", InfoType::ERROR);
231  return $response->withJson($err->getArray(), $err->getCode());
232  }
233  }
234 }
Contains the constants and helpers for authentication of user.
Definition: Auth.php:24
static getUserId()
Get the current user's id.
Definition: Auth.php:68
static getGroupId()
Get the current user's group id.
Definition: Auth.php:80
Fossology exception.
Definition: Exception.php:15
Controller for OSSelot REST API endpoints.
Base controller for REST calls.
getParsedBody(ServerRequestInterface $request)
Parse request body as JSON and return associative PHP array.
Override Slim response for withJson function.
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_find($pluginName)
Given the official name of a plugin, return the $Plugins object.
char * trim(char *ptext)
Trimming whitespace.
Definition: fossconfig.c:690