diff --git a/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java index bce5c598ce..3fdd9a2bee 100644 --- a/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java +++ b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java @@ -78,6 +78,7 @@ private void loadToConfigsInMemForSw360(ConfigContainer configContainer) { .put(IS_BULK_RELEASE_DELETING_ENABLED, getOrDefault(configContainer, IS_BULK_RELEASE_DELETING_ENABLED, "false")) .put(DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, getOrDefault(configContainer, DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, "false")) .put(IS_FORCE_UPDATE_ENABLED, getOrDefault(configContainer, IS_FORCE_UPDATE_ENABLED, "false")) + .put(LICENSE_MANUAL_CREATION_ENABLED, getOrDefault(configContainer, LICENSE_MANUAL_CREATION_ENABLED, "false")) .put(SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE, getOrDefault(configContainer, SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE, UserGroup.USER.name())) .put(TOOL_NAME, getOrDefault(configContainer, TOOL_NAME, SW360Constants.DEFAULT_SBOM_TOOL_NAME)) .put(TOOL_VENDOR, getOrDefault(configContainer, TOOL_VENDOR, SW360Constants.DEFAULT_SBOM_TOOL_VENDOR)) @@ -197,6 +198,7 @@ private boolean isConfigValid(String configKey, String configValue) { MAIL_REQUEST_FOR_PROJECT_REPORT, MAIL_REQUEST_FOR_COMPONENT_REPORT, IS_FORCE_UPDATE_ENABLED, + LICENSE_MANUAL_CREATION_ENABLED, DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, IS_BULK_RELEASE_DELETING_ENABLED, IS_PACKAGE_PORTLET_ENABLED, diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java index 692f94417c..118845afee 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java @@ -47,6 +47,9 @@ public class SW360ConfigKeys { // This property enable force update feature public static final String IS_FORCE_UPDATE_ENABLED = "rest.force.update.enabled"; + // This property is used to enable manual license creation + public static final String LICENSE_MANUAL_CREATION_ENABLED = "license.manual.creation.enabled"; + // This property is used to control the user role for SBOM import and export public static final String SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE = "sbom.import.export.access.usergroup"; @@ -131,6 +134,7 @@ public class SW360ConfigKeys { SKIP_DOMAINS_FOR_VALID_SOURCE_CODE, ATTACHMENT_STORE_FILE_SYSTEM_LOCATION, IS_FORCE_UPDATE_ENABLED, + LICENSE_MANUAL_CREATION_ENABLED, AUTO_SET_ECC_STATUS, IS_ADMIN_PRIVATE_ACCESS_ENABLED, DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, @@ -146,7 +150,8 @@ public class SW360ConfigKeys { ATTACHMENT_DELETE_NO_OF_DAYS, ATTACHMENT_STORE_FILE_SYSTEM_LOCATION, COMBINED_CLI_PARSER_EXTERNAL_ID_CORRELATION_KEY, AUTO_SET_ECC_STATUS, MAIL_REQUEST_FOR_PROJECT_REPORT, MAIL_REQUEST_FOR_COMPONENT_REPORT, IS_BULK_RELEASE_DELETING_ENABLED, - DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, IS_FORCE_UPDATE_ENABLED, SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE, + DISABLE_CLEARING_FOSSOLOGY_REPORT_DOWNLOAD, IS_FORCE_UPDATE_ENABLED, LICENSE_MANUAL_CREATION_ENABLED, + SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE, TOOL_NAME, TOOL_VENDOR, IS_PACKAGE_PORTLET_ENABLED, PACKAGE_PORTLET_WRITE_ACCESS_USER_ROLE, INHERIT_ATTACHMENT_USAGES, RELEASE_FRIENDLY_URL, IS_ADMIN_PRIVATE_ACCESS_ENABLED, SKIP_DOMAINS_FOR_VALID_SOURCE_CODE, VCS_HOSTS, NON_PKG_MANAGED_COMPS_PROP, REST_API_TOKEN_LENGTH, diff --git a/rest/resource-server/src/docs/asciidoc/licenses.adoc b/rest/resource-server/src/docs/asciidoc/licenses.adoc index bb90e08ce4..d3e37c289a 100644 --- a/rest/resource-server/src/docs/asciidoc/licenses.adoc +++ b/rest/resource-server/src/docs/asciidoc/licenses.adoc @@ -86,6 +86,9 @@ include::{snippets}/should_document_get_license/links.adoc[] ==== Create a license A `POST` request will create a license. +Manual license creation is deprecated and disabled by default. To enable it, set `license.manual.creation.enabled` +to `true` in SW360 configurations. If disabled, the API responds with `403 Forbidden` and a message instructing +you to sync licenses from LicenseDB. ===== Response structure include::{snippets}/should_document_create_license/response-fields.adoc[] @@ -280,4 +283,4 @@ A `DELETE` request will delete a single license type. include::{snippets}/should_document_delete_license_type/curl-request.adoc[] ===== Example response -include::{snippets}/should_document_delete_license_type/http-response.adoc[] \ No newline at end of file +include::{snippets}/should_document_delete_license_type/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index f0493cd1a8..5705553d16 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.SW360ConfigKeys; import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.permissions.PermissionUtils; import org.eclipse.sw360.datahandler.resourcelists.PaginationOptions; @@ -823,6 +824,9 @@ private void isLicenseValid(Set licenses) { } private void createMissingLicense(String licenseId) throws Exception { + if (!SW360Utils.readConfig(SW360ConfigKeys.LICENSE_MANUAL_CREATION_ENABLED, false)) { + throw new BadRequestClientException("Manual license creation is disabled. Please sync licenses from LicenseDB."); + } License newLicense = new License(); newLicense.setId(licenseId); newLicense.setShortname(licenseId); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java index 3da2f5ecbd..4d3b9a4f4a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java @@ -29,6 +29,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.common.SW360ConfigKeys; +import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.SW360Constants; import org.eclipse.sw360.datahandler.resourcelists.PaginationParameterException; @@ -102,6 +104,10 @@ public class LicenseController implements RepresentationModelProcessor RESPONSE_BODY_FOR_MODERATION_REQUEST = ImmutableMap.builder() .put("message", "Moderation request is created").build(); + private static final ImmutableMap RESPONSE_BODY_FOR_LICENSE_CREATION_DISABLED = ImmutableMap.builder() + .put("message", "Manual license creation is disabled. Please sync licenses from LicenseDB.").build(); + private static final String LICENSE_CREATION_DEPRECATION_WARNING = + "299 - \"Manual license creation is deprecated; sync from LicenseDB.\""; @Operation( summary = "List all of the service's licenses.", @@ -231,14 +237,21 @@ public ResponseEntity deleteLicense( ) @PreAuthorize("hasAuthority('WRITE')") @PostMapping(value = LICENSES_URL) - public ResponseEntity> createLicense( + public ResponseEntity createLicense( @Parameter(description = "The license to be created.") @RequestBody License license ) throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + if (!SW360Utils.readConfig(SW360ConfigKeys.LICENSE_MANUAL_CREATION_ENABLED, false)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .header("Warning", LICENSE_CREATION_DEPRECATION_WARNING) + .body(RESPONSE_BODY_FOR_LICENSE_CREATION_DISABLED); + } List sw360Licenses = licenseService.getLicenses(); if(restControllerHelper.checkDuplicateLicense(sw360Licenses, license.shortname)) { - return new ResponseEntity("sw360 license with name " + license.shortname + " already exists.", HttpStatus.CONFLICT); + return ResponseEntity.status(HttpStatus.CONFLICT) + .header("Warning", LICENSE_CREATION_DEPRECATION_WARNING) + .body("sw360 license with name " + license.shortname + " already exists."); } license = licenseService.createLicense(license, sw360User); HalResource halResource = createHalLicense(license); @@ -247,7 +260,9 @@ public ResponseEntity> createLicense( .fromCurrentRequest().path("/{id}") .buildAndExpand(license.getId()).toUri(); - return ResponseEntity.created(location).body(halResource); + return ResponseEntity.created(location) + .header("Warning", LICENSE_CREATION_DEPRECATION_WARNING) + .body(halResource); } @PreAuthorize("hasAuthority('WRITE')") diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/LicenseTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/LicenseTest.java index e9f2365340..70156c0f74 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/LicenseTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/LicenseTest.java @@ -239,18 +239,10 @@ public void should_create_license() throws IOException, TException { requestEntity, String.class); - assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); assertNotNull(response.getBody()); - - String responseBody = response.getBody(); - assertTrue("Response should contain created license fullName", responseBody.contains("fullName")); - assertTrue("Response should contain created license shortName", responseBody.contains("shortName")); - assertTrue("Response should contain Apache 3.0", responseBody.contains("Apache 3.0")); - assertTrue("Response should contain Apache License 3.0", responseBody.contains("Apache License 3.0")); - assertTrue("Response should contain checked field", responseBody.contains("checked")); - assertTrue("Response should contain OSIApproved field", responseBody.contains("OSIApproved")); - assertTrue("Response should contain FSFLibre field", responseBody.contains("FSFLibre")); - assertTrue("Response should contain _links", responseBody.contains("_links")); + assertTrue("Response should instruct to sync licenses from LicenseDB", + response.getBody().contains("LicenseDB")); } @Test @@ -529,7 +521,7 @@ public void should_handle_exception_in_create_license() throws IOException, TExc requestEntity, String.class); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); } @Test diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java index 5e8c0f61e7..d96f545092 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java @@ -10,6 +10,8 @@ package org.eclipse.sw360.rest.resourceserver.restdocs; import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.common.SW360ConfigKeys; +import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.licenses.LicenseType; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -26,6 +28,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -53,6 +56,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @RunWith(SpringJUnit4ClassRunner.class) public class LicenseSpecTest extends TestRestDocsSpecBase { @@ -306,24 +310,29 @@ public void should_document_create_license() throws Exception { Map licenseRequestBody = new HashMap<>(); licenseRequestBody.put("fullName", "Apache 3.0"); licenseRequestBody.put("shortName", "Apache License 3.0"); - this.mockMvc.perform(post("/api/licenses") - .contentType(MediaTypes.HAL_JSON) - .content(this.objectMapper.writeValueAsString(licenseRequestBody)) - .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword))) - .andExpect(status().isCreated()) - .andDo(this.documentationHandler.document( - requestFields( - fieldWithPath("fullName").description("The fullName of the new license"), - fieldWithPath("shortName").description("The shortname of the origin license") - ), - responseFields( - fieldWithPath("fullName").description("The fullName of the license"), - fieldWithPath("shortName").description("The shortname of the license"), - fieldWithPath("checked").description("The information, whether the license is already checked, optional and defaults to true"), - subsectionWithPath("OSIApproved").description("The OSI aprroved information, possible values are: " + Arrays.asList(Quadratic.values())), - fieldWithPath("FSFLibre").description("The FSF libre information, possible values are: " + Arrays.asList(Quadratic.values())), - subsectionWithPath("_links").description("<> to other resources") - ))); + try (MockedStatic sw360UtilsMock = Mockito.mockStatic(SW360Utils.class)) { + sw360UtilsMock.when(() -> SW360Utils.readConfig(SW360ConfigKeys.LICENSE_MANUAL_CREATION_ENABLED, false)) + .thenReturn(true); + this.mockMvc.perform(post("/api/licenses") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(licenseRequestBody)) + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword))) + .andExpect(status().isCreated()) + .andExpect(header().string("Warning", "299 - \"Manual license creation is deprecated; sync from LicenseDB.\"")) + .andDo(this.documentationHandler.document( + requestFields( + fieldWithPath("fullName").description("The fullName of the new license"), + fieldWithPath("shortName").description("The shortname of the origin license") + ), + responseFields( + fieldWithPath("fullName").description("The fullName of the license"), + fieldWithPath("shortName").description("The shortname of the license"), + fieldWithPath("checked").description("The information, whether the license is already checked, optional and defaults to true"), + subsectionWithPath("OSIApproved").description("The OSI aprroved information, possible values are: " + Arrays.asList(Quadratic.values())), + fieldWithPath("FSFLibre").description("The FSF libre information, possible values are: " + Arrays.asList(Quadratic.values())), + subsectionWithPath("_links").description("<> to other resources") + ))); + } } @Test