Repository: eclipse-sw360/sw360
Pull request #3146: fix(Rest): Only admin users can delete license types in the admin license type.
Author: login: nikkuma7
Labels: name: ready
description: ready to merge
Created: 2025-05-09T06:43:57Z | Merged: 2025-05-21T05:09:35Z

…ense tab.

[//]: # (This program and the accompanying materials are made)
[//]: # (available under the terms of the Eclipse Public License 2.0)
[//]: # (which is available at https://www.eclipse.org/legal/epl-2.0/)
[//]: # (SPDX-License-Identifier: EPL-2.0)

> Please provide a summary of your changes here.
> * Which issue is this pull request belonging to and how is it solving it? (*Refer to issue here*)
> * Did you add or update any new dependencies that are required for your change?

Issue: #3114 
### Suggest Reviewer
> You can suggest reviewers here with an @mention.

### How To Test?
> How should these changes be tested by the reviewer?
> Have you implemented any additional tests?
endpoint: 
http://localhost:8080/resource/api/licenses/6d990cf76cc16de6068e32ebd100091b/deleteLicenseType

![image](https://github.com/user-attachments/assets/06aa7a3b-d440-4139-9760-da1144b64116)


### Checklist
Must:
- [ ] All related issues are referenced in commit messages and in PR



Diff:
diff --git a/rest/resource-server/src/docs/asciidoc/licenses.adoc b/rest/resource-server/src/docs/asciidoc/licenses.adoc
index d2321add43..bb90e08ce4 100644
--- a/rest/resource-server/src/docs/asciidoc/licenses.adoc
+++ b/rest/resource-server/src/docs/asciidoc/licenses.adoc
@@ -269,4 +269,15 @@ include::{snippets}/should_document_get_license_report/query-parameters.adoc[]
 include::{snippets}/should_document_get_license_report/curl-request.adoc[]
 
 ===== Example response
-include::{snippets}/should_document_get_license_report/http-response.adoc[]
\ No newline at end of file
+include::{snippets}/should_document_get_license_report/http-response.adoc[]
+
+[[resources-license-type-delete]]
+==== Delete a single license type
+
+A `DELETE` request will delete a single license type.
+
+===== Example request
+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
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 62c946046e..055c975459 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
@@ -540,4 +540,21 @@ public ResponseEntity<RequestStatus> createLicenseType(
         HttpStatus status = HttpStatus.OK;
         return new ResponseEntity<>(requestStatus, status);
     }
+
+    @Operation(
+            summary = "Delete a specific license type.",
+            description = "Delete a specific license type.",
+            tags = {"Licenses"}
+    )
+    @PreAuthorize("hasAuthority('WRITE')")
+    @RequestMapping(value = LICENSE_TYPES_URL + "/{id}", method = RequestMethod.DELETE)
+    public ResponseEntity deleteLicenseType(
+            @Parameter(description = "The id of the license type.")
+            @PathVariable("id") String id
+    ) throws TException {
+        User sw360User = restControllerHelper.getSw360UserFromAuthentication();
+        RequestStatus status = licenseService.deleteLicenseType(id, sw360User);
+
+        return ResponseEntity.ok("License type deleted successfully: " + status.name());
+    }
 }
diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/Sw360LicenseService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/Sw360LicenseService.java
index 8d2c2af6f7..9a5b41746e 100644
--- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/Sw360LicenseService.java
+++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/Sw360LicenseService.java
@@ -39,10 +39,12 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.rest.webmvc.ResourceNotFoundException;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.stereotype.Service;
 import org.springframework.util.FileCopyUtils;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+
 import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.io.InputStream;
@@ -340,4 +342,25 @@ public List<LicenseType> quickSearchLicenseType(String searchElem) {
              throw new RuntimeException("Error performing licenseType search", e);
          }
      }
+
+     public RequestStatus deleteLicenseType(String id, User sw360User) throws TException {
+         LicenseService.Iface sw360LicenseClient = getThriftLicenseClient();
+
+         if (!PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) {
+             throw new AccessDeniedException("Unable to delete license type. User is not admin");
+         }
+
+         try {
+             sw360LicenseClient.getLicenseTypeById(id);
+         } catch (TException e) {
+             throw new ResourceNotFoundException("License type not found with ID: " + id);
+         }
+
+         RequestStatus status = sw360LicenseClient.deleteLicenseType(id, sw360User);
+         if (status == RequestStatus.FAILURE) {
+             throw new SW360Exception("License type could not be deleted.");
+         }
+
+         return status;
+     }
 }
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 28e363af26..5a0103a5d5 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
@@ -121,6 +121,7 @@ public void before() throws TException, IOException {
         Mockito.doNothing().when(licenseServiceMock).importSpdxInformation(any());
         Mockito.doNothing().when(licenseServiceMock).getDownloadLicenseArchive(any(), any(), any());
         Mockito.doNothing().when(licenseServiceMock).uploadLicense(any(), any(), anyBoolean(), anyBoolean());
+        given(this.licenseServiceMock.deleteLicenseType(any(), any())).willReturn(RequestStatus.SUCCESS);
         given(this.licenseServiceMock.importOsadlInformation(any())).willReturn(requestSummary);
         given(this.licenseServiceMock.addLicenseType(any(),any() , any())).willReturn(RequestStatus.SUCCESS);
         given(this.sw360ReportServiceMock.getLicenseBuffer()).willReturn(ByteBuffer.allocate(10000));
@@ -455,4 +456,12 @@ public void should_document_get_license_report() throws Exception{
                             parameterWithName("module").description("module represent the licenses. Possible values are `<licenses>`")
                     )));
     }
+
+    @Test
+    public void should_document_delete_license_type() throws Exception {
+        mockMvc.perform(delete("/api/licenseTypes/" + license.getId())
+                .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword))
+                .accept(MediaTypes.HAL_JSON))
+        .andExpect(status().isOk());
+    }
 }
\ No newline at end of file


Reviews:
user: login: GMishx
body: Please use exceptions whenever possible.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-14T12:19:26Z
user: login: GMishx
body: One minor change still pending . :-)
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T09:07:07Z
user: login: GMishx
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-20T07:32:25Z
user: login: GMishx
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T08:19:04Z

Review comments:
diff_hunk: @@ -526,4 +526,31 @@ public ResponseEntity<RequestStatus> createLicenseType(
         HttpStatus status = HttpStatus.OK;
         return new ResponseEntity<>(requestStatus, status);
     }
+
+    @Operation(
+            summary = "Delete a specific license type.",
+            description = "Delete a specific license type.",
+            tags = {"Licenses"}
+    )
+    @PreAuthorize("hasAuthority('WRITE')")
+    @RequestMapping(value = LICENSES_URL + "/{id}/deleteLicenseType", method = RequestMethod.DELETE)
+    public ResponseEntity deleteLicenseType(
+            @Parameter(description = "The id of the license type.")
+            @PathVariable("id") String id
+    ) throws TException {
+        User sw360User = restControllerHelper.getSw360UserFromAuthentication();
+        try {
+            RequestStatus status = licenseService.deleteLicenseType(id, sw360User);
+
+            if (status == RequestStatus.FAILURE) {
+                return new ResponseEntity<>("License type not found or could not be deleted", HttpStatus.NOT_FOUND);
+            }
+
+            return new ResponseEntity<>(status.name(), HttpStatus.OK);
+        } catch (BadRequestClientException e) {
+            return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
+        } catch (TException e) {
+            return new ResponseEntity<>("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
path: rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java
user: login: GMishx
body: Please throw appropriate exceptions:
```suggestion
                throw new ResourceNotFoundException("License type not found or could not be deleted");
            }

            return new ResponseEntity<>(status.name(), HttpStatus.OK);
        } catch (TException e) {
            throw new SW360Exception("Internal server error");
```
created_at: 2025-05-14T12:18:33Z
author_association: MEMBER
diff_hunk: @@ -315,4 +316,17 @@ public RequestStatus addLicenseType(User sw360User, String licenseType, HttpServ
          }
          return RequestStatus.SUCCESS;
      }
-}
+
+     public RequestStatus deleteLicenseType(String id, User sw360User) throws TException {
+         LicenseService.Iface sw360LicenseClient = getThriftLicenseClient();
+         if (!PermissionUtils.isUserAtLeast(UserGroup.ADMIN, sw360User)) {
+             throw new BadRequestClientException("Unable to delete license type. User is not admin");
path: rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/Sw360LicenseService.java
user: login: GMishx
body: ```suggestion
             throw new AccessDeniedException("Unable to delete license type. User is not admin");
```
created_at: 2025-05-14T12:18:58Z
author_association: MEMBER
diff_hunk: @@ -526,4 +526,21 @@ public ResponseEntity<RequestStatus> createLicenseType(
         HttpStatus status = HttpStatus.OK;
         return new ResponseEntity<>(requestStatus, status);
     }
+
+    @Operation(
+            summary = "Delete a specific license type.",
+            description = "Delete a specific license type.",
+            tags = {"Licenses"}
+    )
+    @PreAuthorize("hasAuthority('WRITE')")
+    @RequestMapping(value = LICENSES_URL + "/{id}/deleteLicenseType", method = RequestMethod.DELETE)
path: rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java
user: login: GMishx
body: Following REST API practices, the request should look like
```suggestion
    @RequestMapping(value = LICENSES_URL + "/{id}", method = RequestMethod.DELETE)
```
created_at: 2025-05-15T09:06:29Z
author_association: MEMBER
diff_hunk: @@ -540,4 +540,21 @@ public ResponseEntity<RequestStatus> createLicenseType(
         HttpStatus status = HttpStatus.OK;
         return new ResponseEntity<>(requestStatus, status);
     }
+
+    @Operation(
+            summary = "Delete a specific license type.",
+            description = "Delete a specific license type.",
+            tags = {"Licenses"}
+    )
+    @PreAuthorize("hasAuthority('WRITE')")
+    @RequestMapping(value = LICENSE_TYPES_URL + "/{id}/", method = RequestMethod.DELETE)
path: rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/license/LicenseController.java
user: login: GMishx
body: The endpoints should not end with `/`
```suggestion
    @RequestMapping(value = LICENSE_TYPES_URL + "/{id}", method = RequestMethod.DELETE)
```
created_at: 2025-05-20T07:32:21Z
author_association: MEMBER

PR comments:
user: login: amritkv
created_at: 2025-05-13T07:23:40Z
body: Hey @nikkuma7 !
Tested the functionality and it's working fine ! 👍🏻 
author_association: MEMBER
user: login: nikkuma7
created_at: 2025-05-15T06:37:32Z
body: comment addressed.
author_association: CONTRIBUTOR