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