diff --git a/.github/workflows/docker_deploy.yml b/.github/workflows/docker_deploy.yml index e890990704..88d4efb518 100644 --- a/.github/workflows/docker_deploy.yml +++ b/.github/workflows/docker_deploy.yml @@ -173,7 +173,7 @@ jobs: type=ref,event=tag - name: Build image - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: . target: sw360 diff --git a/backend/common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java b/backend/common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java index cd888b1f07..c0eeb7f690 100644 --- a/backend/common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java +++ b/backend/common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java @@ -154,7 +154,7 @@ private Map> getVcsToComponentMap(Li } @SuppressWarnings("unchecked") - public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user) { + public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user, boolean doNotReplacePackageAndRelease) { RequestSummary requestSummary = new RequestSummary(); Map messageMap = new HashMap<>(); requestSummary.setRequestStatus(RequestStatus.FAILURE); @@ -192,15 +192,14 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a if (!IS_PACKAGE_PORTLET_ENABLED) { vcsToComponentMap.put("", components); - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease); } else { vcsToComponentMap = getVcsToComponentMap(components); if (componentsCount == vcsCount) { - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease); } else if (componentsCount > vcsCount) { - - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease); if (requestSummary.requestStatus.equals(RequestStatus.SUCCESS)) { @@ -365,7 +364,7 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a } public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMetadata, - Map> vcsToComponentMap, String projectId, AttachmentContent attachmentContent) + Map> vcsToComponentMap, String projectId, AttachmentContent attachmentContent, boolean doNotReplacePackageAndRelease) throws SW360Exception { final RequestSummary summary = new RequestSummary(); summary.setRequestStatus(RequestStatus.FAILURE); @@ -418,7 +417,7 @@ public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMeta } if (IS_PACKAGE_PORTLET_ENABLED) { - messageMap = importAllComponentsAsPackages(vcsToComponentMap, project); + messageMap = importAllComponentsAsPackages(vcsToComponentMap, project, doNotReplacePackageAndRelease); } else { messageMap = importAllComponentsAsReleases(vcsToComponentMap, project); } @@ -548,8 +547,7 @@ private Map importAllComponentsAsReleases(Map importAllComponentsAsPackages(Map> vcsToComponentMap, Project project) { - + private Map importAllComponentsAsPackages(Map> vcsToComponentMap, Project project, boolean doNotReplacePackageAndRelease) throws SW360Exception { final var countMap = new HashMap(); final Set duplicateComponents = new HashSet<>(); final Set duplicateReleases = new HashSet<>(); @@ -557,10 +555,17 @@ private Map importAllComponentsAsPackages(Map invalidReleases = new HashSet<>(); final Set invalidPackages = new HashSet<>(); final Map releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage(); + final Set projectPkgIds = CommonUtils.isNullOrEmptyCollection(project.getPackageIds()) ? new HashSet<>() : project.getPackageIds(); countMap.put(REL_CREATION_COUNT_KEY, 0); countMap.put(REL_REUSE_COUNT_KEY, 0); countMap.put(PKG_CREATION_COUNT_KEY, 0); countMap.put(PKG_REUSE_COUNT_KEY, 0); int relCreationCount = 0, relReuseCount = 0, pkgCreationCount = 0, pkgReuseCount = 0; + if (!doNotReplacePackageAndRelease) { + releaseRelationMap.clear(); + projectPkgIds.clear(); + log.info("Cleared existing releases and packages for project: " + project.getName()); + } + for (Map.Entry> entry : vcsToComponentMap.entrySet()) { Component comp = createComponent(entry.getKey()); List componentsFromBom = entry.getValue(); diff --git a/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java index e540d4f146..cbd314956a 100644 --- a/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java +++ b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java @@ -1859,6 +1859,10 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen } public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception { + return importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, false); + } + + public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws SW360Exception { final AttachmentContent attachmentContent = attachmentConnector.getAttachmentContent(attachmentContentId); final Duration timeout = Duration.durationOf(30, TimeUnit.SECONDS); try { @@ -1867,7 +1871,7 @@ public RequestSummary importCycloneDxFromAttachmentContent(User user, String att .unsafeGetAttachmentStream(attachmentContent)) { final CycloneDxBOMImporter cycloneDxBOMImporter = new CycloneDxBOMImporter(this, componentDatabaseHandler, packageDatabaseHandler, attachmentConnector, user); - return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user); + return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user, doNotReplacePackageAndRelease); } } catch (IOException e) { log.error("Error while importing / parsing CycloneDX SBOM! ", e); diff --git a/backend/projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java b/backend/projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java index 2bc82802b9..65140fed52 100644 --- a/backend/projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java +++ b/backend/projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java @@ -274,6 +274,13 @@ public RequestSummary importCycloneDxFromAttachmentContent(User user, String att return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId); } + @Override + public RequestSummary importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws SW360Exception { + assertId(attachmentContentId); + assertUser(user); + return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, doNotReplacePackageAndRelease); + } + @Override public RequestSummary exportCycloneDxSbom(String projectId, String bomType, boolean includeSubProjReleases, User user) throws SW360Exception { assertId(projectId); diff --git a/libraries/datahandler/src/main/thrift/projects.thrift b/libraries/datahandler/src/main/thrift/projects.thrift index d6db26574d..8b8ad981d5 100644 --- a/libraries/datahandler/src/main/thrift/projects.thrift +++ b/libraries/datahandler/src/main/thrift/projects.thrift @@ -612,6 +612,12 @@ service ProjectService { */ RequestSummary importCycloneDxFromAttachmentContent(1: User user, 2: string attachmentContentId, 3: string projectId) throws (1: SW360Exception exp); + /** + * Parse a CycloneDx SBoM file (XML or JSON) during re-import on a project and write the information to SW360 as Project / Component / Release / Package + * with replaceReleaseAndPackageFlag + */ + RequestSummary importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(1: User user, 2: string attachmentContentId, 3: string projectId, 4: bool doNotReplacePackageAndRelease) throws (1: SW360Exception exp); + /** * Export a CycloneDx SBoM file (XML or JSON) for a Project */ diff --git a/pom.xml b/pom.xml index 4f326a7024..5c8eea4f47 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 3.4.2 5.1.8 3.13.0 - 3.3.0 + 3.8.1 3.5.0 3.3.0 3.4.2 @@ -92,7 +92,7 @@ 2.9.0 2.4.10 4.0.1 - 2.18.1 + 2.18.2 3.26.3 @@ -105,7 +105,7 @@ 1.10 1.12.0 2.17.0 - 3.12.0 + 3.17.0 1.12.0 2.1.4 3.17.3 @@ -150,17 +150,13 @@ 3.3.3 3.0.1 1.1.1.RELEASE - 1.3.3 + 1.4.0 6.4.1 - 2.6.0 - 2.6.0 - 2.6.0 - 1.7.0 - 2.6.0 + 2.7.0 6.2.0 0.20.0 1.28.5 - 2.26.0 + 2.35.2 1.0.0.Final 2.5.4 2.3.1 diff --git a/rest/resource-server/pom.xml b/rest/resource-server/pom.xml index fad7e2e264..fca46039d4 100644 --- a/rest/resource-server/pom.xml +++ b/rest/resource-server/pom.xml @@ -197,7 +197,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - ${springdoc-openapi-webmvc.version} + ${springdoc-openapi-stater-common.version} org.springdoc diff --git a/rest/resource-server/src/docs/asciidoc/projects.adoc b/rest/resource-server/src/docs/asciidoc/projects.adoc index e753676ba0..1321901b7b 100644 --- a/rest/resource-server/src/docs/asciidoc/projects.adoc +++ b/rest/resource-server/src/docs/asciidoc/projects.adoc @@ -930,7 +930,15 @@ include::{snippets}/should_document_import_cyclonedx/http-response.adoc[] [[resources-import-sbom-on-project]] ==== Import SBOM on project -A `POST` request is used to import a SBOM on a project. Currently only CycloneDX(.xml/.json) files are supported. +A `POST` request is used to import a SBOM on a project. The project’s releases and packages will be replaced with the latest data from the SBOM. Currently only CycloneDX(.xml/.json) files are supported. + +[red]#Request parameter# +|=== +|Parameter |Description + +|doNotReplacePackageAndRelease +|When importing an SBOM into an existing project, the project’s releases and packages will be replaced with the latest data from the SBOM. Use the parameter `doNotReplacePackageAndRelease=true` to import new data without replacing the existing releases and packages. +|=== [red]#Request body# |=== diff --git a/rest/resource-server/src/docs/asciidoc/schedule.adoc b/rest/resource-server/src/docs/asciidoc/schedule.adoc index fc7b8d1baa..4e5b70a6fb 100644 --- a/rest/resource-server/src/docs/asciidoc/schedule.adoc +++ b/rest/resource-server/src/docs/asciidoc/schedule.adoc @@ -90,3 +90,102 @@ include::{snippets}/should_document_schedule_cve_search/curl-request.adoc[] ===== Example response include::{snippets}/should_document_schedule_cve_search/http-response.adoc[] + +[[schedule-svmsync]] +==== Schedule svm sync for user. + +A `POST` request will schedule svm sync for user. + +===== Example request +include::{snippets}/should_document_schedule_svm_sync/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_schedule_svm_sync/http-response.adoc[] + +[[unschedule-svmSync]] +==== Cancel schedule svm sync for user. + +A `DELETE` request will cancel schedule svm sync for user. + +===== Example request +include::{snippets}/should_document_cancel_schedule_svm_sync/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_cancel_schedule_svm_sync/http-response.adoc[] + +[[reverse-svmmatch]] +==== Svm reverse match for user. + +A `POST` request will reverse svm match. + +===== Example request +include::{snippets}/should_document_reverse_svm_match/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_reverse_svm_match/http-response.adoc[] + +[[unschedule-reversematch]] +==== Cancel reverse match for user. + +A `DELETE` request will cancel the reverse match. + +===== Example request +include::{snippets}/should_document_cancel_reverse_match/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_cancel_reverse_match/http-response.adoc[] + +[[tracking-feedback]] +==== Tracking feedback for user. + +A `POST` request will track the user feedback. + +===== Example request +include::{snippets}/should_document_track_feedback/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_track_feedback/http-response.adoc[] + +[[monitoring-listUpdate]] +==== Monitoring a svm list Update. + +A `POST` request will update svm list. + +===== Example request +include::{snippets}/should_document_svm_list_update/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_svm_list_update/http-response.adoc[] + +[[cancel-monitoringlist]] +==== Cancel monitoring svm list and update. + +A `DELETE` request will cancel the monitoring svm list and update. + +===== Example request +include::{snippets}/should_document_cancel_monitoring_svm_list/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_cancel_monitoring_svm_list/http-response.adoc[] + +[[src-upload]] +==== Source attachment upload service. + +A `POST` request will upload the source attachment. + +===== Example request +include::{snippets}/should_document_src_upload/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_src_upload/http-response.adoc[] + +[[cancel-srcupload]] +==== Cancel source attachment upload service. + +A `DELETE` request will cancel the src upload. + +===== Example request +include::{snippets}/should_document_cancel_monitoring_cancel_svm_list/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_cancel_monitoring_cancel_svm_list/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java index 58a96a36d2..1dc8104e3f 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java @@ -2063,7 +2063,7 @@ public ResponseEntity importSBOM( } projectId = requestSummary.getMessage(); } else { - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), ""); + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), "", true); if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); @@ -2117,7 +2117,9 @@ public ResponseEntity importSBOMonProject( @Parameter(description = "Project ID", example = "376576") @PathVariable(value = "id", required = true) String id, @Parameter(description = "SBOM file") - @RequestBody MultipartFile file + @RequestBody MultipartFile file, + @Parameter(description = "Don't overwrite existing project releases and packages while re-importing SBOM") + @RequestParam(value = "doNotReplacePackageAndRelease", required = false) boolean doNotReplacePackageAndRelease ) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Attachment attachment = null; @@ -2132,7 +2134,7 @@ public ResponseEntity importSBOMonProject( throw new RuntimeException("failed to upload attachment", e); } - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id); + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id, doNotReplacePackageAndRelease); if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java index d9968e47ef..b2f22779c2 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java @@ -1150,9 +1150,9 @@ public RequestSummary importSPDX(User user, String attachmentContentId) throws T * @return RequestSummary * @throws TException */ - public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId) throws TException { + public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws TException { ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); - return sw360ProjectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId)); + return sw360ProjectClient.importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId), doNotReplacePackageAndRelease); } /** diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/ScheduleAdminController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/ScheduleAdminController.java index 217a37bd2f..97efe8d2d3 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/ScheduleAdminController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/ScheduleAdminController.java @@ -15,15 +15,16 @@ import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.RequestSummary; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; -import org.eclipse.sw360.datahandler.thrift.RequestSummary; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.data.rest.webmvc.RepositoryLinksResource; import org.springframework.hateoas.server.RepresentationModelProcessor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -50,7 +51,6 @@ public class ScheduleAdminController implements RepresentationModelProcessor unscheduleAllServices()throws TException { example = "SUCCESS"))) }) @PostMapping(SCHEDULE_URL + "/cveService") - public ResponseEntity scheduleCve()throws TException { + public ResponseEntity scheduleCve() throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); RequestSummary requestSummary = scheduleService.scheduleCveSearch(sw360User); HttpStatus status = HttpStatus.ACCEPTED; return new ResponseEntity<>(requestSummary, status); } + @Operation( + summary = "Schedule the SVM sync.", + description = "Manually schedule the SVM Sync service.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @PostMapping(SCHEDULE_URL + "/scheduleSvmSync") + public ResponseEntity scheduleSvmSync()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestSummary requestSummary = scheduleService.svmSync(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestSummary, status); + } + @Operation( summary = "Unschedule the CVE service.", description = "Unschedule the CVE service.", @@ -107,13 +126,32 @@ public ResponseEntity scheduleCve()throws TException { example = "SUCCESS"))) }) @PostMapping(SCHEDULE_URL + "/unscheduleCve") - public ResponseEntity unscheduleCveSearch()throws TException { + public ResponseEntity unscheduleCveSearch() throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); RequestStatus requestStatus = scheduleService.cancelCveSearch(sw360User); HttpStatus status = HttpStatus.ACCEPTED; return new ResponseEntity<>(requestStatus, status); } + @Operation( + summary = "Cancel scheduled SVM sync.", + description = "Cancel the scheduled SVM Sync service.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @DeleteMapping(SCHEDULE_URL + "/unscheduleSvmSync") + public ResponseEntity unscheduleSvmSync() throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestStatus requestStatus = scheduleService.cancelSvmSync(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestStatus, status); + } + @Operation( summary = "Schedule the attachment deletion service.", description = "Schedule service for attachment deletion from local FS.", @@ -126,13 +164,32 @@ public ResponseEntity unscheduleCveSearch()throws TException { example = "SUCCESS"))) }) @PostMapping(SCHEDULE_URL + "/deleteAttachment") - public ResponseEntity scheduleDeleteAttachment()throws TException { + public ResponseEntity scheduleDeleteAttachment() throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); RequestSummary requestSummary = scheduleService.deleteAttachmentService(sw360User); HttpStatus status = HttpStatus.ACCEPTED; return new ResponseEntity<>(requestSummary, status); } + @Operation( + summary = "Reverse SVM match.", + description = "Reverse SVM match.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @PostMapping(SCHEDULE_URL + "/svmReverseMatch") + public ResponseEntity svmReverseMatch() throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestSummary requestSummary = scheduleService.scheduleSvmReverseMatch(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestSummary, status); + } + @Operation( summary = "Unschedule the attachment deletion service.", description = "Unschedule the service for attachment deletion from local FS.", @@ -152,6 +209,25 @@ public ResponseEntity unscheduleDeleteAttachment()throws TException { return new ResponseEntity<>(requestStatus, status); } + @Operation( + summary = "Cancel scheduled reverse SVM match.", + description = "Cancel the scheduled reverse SVM match service.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @DeleteMapping(SCHEDULE_URL + "/unscheduleSvmReverseMatch") + public ResponseEntity unscheduleSvmReverseMatch()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestStatus requestStatus = scheduleService.cancelSvmReverseMatch(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestStatus, status); + } + @Operation( summary = "Cancel the attachment deletion service.", description = "Cancel service for attachment deletion from local FS.", @@ -164,13 +240,70 @@ public ResponseEntity unscheduleDeleteAttachment()throws TException { example = "SUCCESS"))) }) @PostMapping(SCHEDULE_URL + "/cancelAttachmentDeletion") - public ResponseEntity attachmentDeleteLocalFS()throws TException { + public ResponseEntity attachmentDeleteLocalFS() throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); RequestStatus requestStatus = scheduleService.cancelAttachmentDeletionLocalFS(sw360User); HttpStatus status = HttpStatus.ACCEPTED; return new ResponseEntity<>(requestStatus, status); } + @Operation( + summary = "Track the user feedback.", + description = "Track the user feedback.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @PostMapping(SCHEDULE_URL + "/trackingFeedback") + public ResponseEntity svmTrackingFeedback()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestSummary requestSummary = scheduleService.svmReleaseTrackingFeedback(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestSummary, status); + } + + @Operation( + summary = "Update the SVM list.", + description = "Update the SVM list.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @PostMapping(SCHEDULE_URL + "/monitoringListUpdate") + public ResponseEntity monitoringListUpdate()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestSummary requestSummary = scheduleService.svmMonitoringListUpdate(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestSummary, status); + } + + @Operation( + summary = "Cancel the SVM list update.", + description = "Cancel the scheduled SVM list update.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @DeleteMapping(SCHEDULE_URL + "/cancelMonitoringListUpdate") + public ResponseEntity cancelMonitoringListUpdate()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestStatus requestStatus = scheduleService.cancelSvmMonitoringListUpdate(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestStatus, status); + } + @Operation( summary = "Schedule the CVE search.", description = "Schedule the CVE search.", @@ -189,4 +322,42 @@ public ResponseEntity cveSearch()throws TException { HttpStatus status = HttpStatus.ACCEPTED; return new ResponseEntity<>(requestStatus, status); } + + @Operation( + summary = "Upload the source attachment.", + description = "Upload the source attachment.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @PostMapping(SCHEDULE_URL + "/srcUpload") + public ResponseEntity srcUpload()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestSummary requestSummary = scheduleService.triggeSrcUpload(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestSummary, status); + } + + @Operation( + summary = "Cancel the source attachment upload.", + description = "Cancel the source attachment upload.", + tags = {"Admin"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "202", description = "Status in the body.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = String.class, + example = "SUCCESS"))) + }) + @DeleteMapping(SCHEDULE_URL + "/cancelSrcUpload") + public ResponseEntity cancelsrcUpload()throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + RequestStatus requestStatus = scheduleService.unscheduleSrcUpload(sw360User); + HttpStatus status = HttpStatus.ACCEPTED; + return new ResponseEntity<>(requestStatus, status); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/Sw360ScheduleService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/Sw360ScheduleService.java index 6a79975141..9831e3be38 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/Sw360ScheduleService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/schedule/Sw360ScheduleService.java @@ -106,6 +106,160 @@ public RequestStatus cancelAttachmentDeletionLocalFS(User sw360User) throws TExc } public RequestStatus triggerCveSearch(User sw360User) throws TException { - return new ThriftClients().makeCvesearchClient().update(); + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + ThriftClients thriftClients = new ThriftClients(); + return thriftClients.makeCvesearchClient().update(); + } else { + throw new AccessDeniedException("User does not have admin access"); + } + } catch (SW360Exception sw360Exp) { + if (sw360Exp.getErrorCode() == 403) { + throw new AccessDeniedException("User does not have admin access", sw360Exp); + } else { + throw sw360Exp; + } + } catch (TException e) { + log.error("Error occurred while triggering CVE search: " + e.getMessage()); + throw e; + } catch (Exception e) { + log.error("Unexpected error occurred while triggering CVE search: " + e.getMessage()); + throw new TException("Unexpected error", e); + } + } + + public RequestSummary svmSync(User sw360User) throws TException { + String serviceName = ThriftClients.SVMSYNC_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestSummary requestSummary = new ThriftClients().makeScheduleClient().scheduleService(serviceName); + return requestSummary; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestStatus cancelSvmSync(User sw360User) throws TException { + String serviceName = ThriftClients.SVMSYNC_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestStatus requestStatus = new ThriftClients().makeScheduleClient().unscheduleService(serviceName, sw360User); + return requestStatus; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestSummary scheduleSvmReverseMatch(User sw360User) throws TException { + String serviceName = ThriftClients.SVMMATCH_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestSummary requestSummary = new ThriftClients().makeScheduleClient().scheduleService(serviceName); + return requestSummary; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestStatus cancelSvmReverseMatch(User sw360User) throws TException { + String serviceName = ThriftClients.SVMMATCH_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestStatus requestStatus = new ThriftClients().makeScheduleClient().unscheduleService(serviceName, sw360User); + return requestStatus; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestSummary svmReleaseTrackingFeedback(User sw360User) throws TException { + String serviceName = ThriftClients.SVM_TRACKING_FEEDBACK_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestSummary requestSummary = new ThriftClients().makeScheduleClient().scheduleService(serviceName);; + return requestSummary; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestSummary svmMonitoringListUpdate(User sw360User) throws TException { + String serviceName = ThriftClients.SVM_LIST_UPDATE_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestSummary requestSummary = new ThriftClients().makeScheduleClient().scheduleService(serviceName); + return requestSummary; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestStatus cancelSvmMonitoringListUpdate(User sw360User) throws TException { + String serviceName = ThriftClients.SVM_LIST_UPDATE_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestStatus requestStatus = new ThriftClients().makeScheduleClient().unscheduleService(serviceName, sw360User); + return requestStatus; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestSummary triggeSrcUpload(User sw360User) throws TException { + String serviceName = ThriftClients.SRC_UPLOAD_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestSummary requestSummary = new ThriftClients().makeScheduleClient().scheduleService(serviceName); + return requestSummary; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } + } + + public RequestStatus unscheduleSrcUpload(User sw360User) throws TException { + String serviceName = ThriftClients.SRC_UPLOAD_SERVICE; + try { + if (PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) { + RequestStatus requestStatus = new ThriftClients().makeScheduleClient().unscheduleService(serviceName, sw360User); + return requestStatus; + } else { + throw new AccessDeniedException("User is not admin"); + } + } catch (TException e) { + log.error("Error occurred while scheduling service: " + serviceName, e); + throw e; + } } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java index c71dc6f191..5533d1fdab 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java @@ -575,7 +575,7 @@ public void before() throws TException, IOException { given(this.projectServiceMock.loadPreferredClearingDateLimit()).willReturn(Integer.valueOf(7)); given(this.projectServiceMock.importSPDX(any(),any())).willReturn(requestSummaryForSPDX); - given(this.projectServiceMock.importCycloneDX(any(),any(),any())).willReturn(requestSummaryForCycloneDX); + given(this.projectServiceMock.importCycloneDX(any(),any(),any(),anyBoolean())).willReturn(requestSummaryForCycloneDX); given(this.sw360ReportServiceMock.getDocumentName(any(), any())).willReturn(projectName); given(this.sw360ReportServiceMock.getProjectBuffer(any(),anyBoolean(),any())).willReturn(ByteBuffer.allocate(10000)); given(this.projectServiceMock.getProjectsForUser(any(), any())).willReturn(projectList); @@ -2504,6 +2504,7 @@ public void should_document_import_cyclonedx_on_project() throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/api/projects/"+project.getId()+"/import/SBOM") .content(file.getBytes()) .contentType(MediaType.MULTIPART_FORM_DATA) + .queryParam("doNotReplacePackageAndRelease", "false") .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)); this.mockMvc.perform(builder).andExpect(status().isOk()).andDo(this.documentationHandler.document()); } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ScheduleSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ScheduleSpecTest.java index 7fca8947d0..7789010c65 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ScheduleSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ScheduleSpecTest.java @@ -13,6 +13,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -42,7 +45,6 @@ public class ScheduleSpecTest extends TestRestDocsSpecBase { @MockBean private Sw360ScheduleService scheduleServiceMock; - private RequestSummary requestSummary = new RequestSummary(); @Before @@ -60,6 +62,14 @@ public void before() throws TException { given(this.scheduleServiceMock.cancelDeleteAttachment(any())).willReturn(RequestStatus.SUCCESS); given(this.scheduleServiceMock.cancelAttachmentDeletionLocalFS(any())).willReturn(RequestStatus.SUCCESS); given(this.scheduleServiceMock.triggerCveSearch(any())).willReturn(RequestStatus.SUCCESS); + given(this.scheduleServiceMock.cancelSvmSync(any())).willReturn(RequestStatus.SUCCESS); + given(this.scheduleServiceMock.cancelSvmReverseMatch(any())).willReturn(RequestStatus.SUCCESS); + given(this.scheduleServiceMock.scheduleSvmReverseMatch(any())).willReturn(requestSummary); + given(this.scheduleServiceMock.svmReleaseTrackingFeedback(any())).willReturn(requestSummary); + given(this.scheduleServiceMock.svmMonitoringListUpdate(any())).willReturn(requestSummary); + given(this.scheduleServiceMock.triggeSrcUpload(any())).willReturn(requestSummary); + given(this.scheduleServiceMock.cancelSvmMonitoringListUpdate(any())).willReturn(RequestStatus.SUCCESS); + given(this.scheduleServiceMock.unscheduleSrcUpload(any())).willReturn(RequestStatus.SUCCESS); } @@ -78,6 +88,78 @@ public void should_document_schedule_cve_service() throws Exception { .accept(MediaTypes.HAL_JSON)) .andExpect(status().isAccepted()); } + + @Test + public void should_document_schedule_svm_sync() throws Exception { + mockMvc.perform(post("/api/schedule/scheduleSvmSync") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_cancel_schedule_svm_sync() throws Exception { + mockMvc.perform(delete("/api/schedule/unscheduleSvmSync") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_reverse_svm_match() throws Exception { + mockMvc.perform(post("/api/schedule/svmReverseMatch") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_cancel_reverse_match() throws Exception { + mockMvc.perform(delete("/api/schedule/unscheduleSvmReverseMatch") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_track_feedback() throws Exception { + mockMvc.perform(post("/api/schedule/trackingFeedback") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_svm_list_update() throws Exception { + mockMvc.perform(post("/api/schedule/monitoringListUpdate") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_cancel_monitoring_svm_list() throws Exception { + mockMvc.perform(delete("/api/schedule/cancelMonitoringListUpdate") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_src_upload() throws Exception { + mockMvc.perform(post("/api/schedule/srcUpload") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } + + @Test + public void should_document_cancel_monitoring_cancel_svm_list() throws Exception { + mockMvc.perform(delete("/api/schedule/cancelSrcUpload") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isAccepted()); + } @Test public void should_document_unschedule_cve_search() throws Exception {