From 2d20226e7cab46363be283fd409d1e9ec56d28a1 Mon Sep 17 00:00:00 2001 From: Eldrin Date: Fri, 3 Feb 2023 19:10:31 +0100 Subject: [PATCH 01/20] fix(Script): Modified script such that it removes trailing and leading whitespaces of components and releases and additonally link releases of duplicate components Signed-off-by: Eldrin --- .../053_remove_whitespace_component_name.py | 163 +++++++++++++++--- 1 file changed, 138 insertions(+), 25 deletions(-) diff --git a/scripts/migrations/053_remove_whitespace_component_name.py b/scripts/migrations/053_remove_whitespace_component_name.py index 79f1062d44..887a5802ad 100644 --- a/scripts/migrations/053_remove_whitespace_component_name.py +++ b/scripts/migrations/053_remove_whitespace_component_name.py @@ -35,50 +35,166 @@ def dbConnection(): couch = couchdb.Server(COUCHSERVER) return couch[DBNAME] -def queryExecution(db): - print('Executing the query.................../') - db_query = db.find({"selector":{"type": "component","name": {"$regex": "(^\\s.+)|(.+\\s$)"}},"limit":999999999999999}) +def queryExecution(db,type): + print(f'Executing the {type} whitespace query.................../') + db_query = db.find({"selector":{"type": type,"name": {"$regex": "(^\\s.+)|(.+\\s$)"}},"limit":999999999999999}) if bool(db_query): return list(db_query) else: return [] def updateDB(db,db_copy_list): - if not DRY_RUN: - print('Correcting the component names and updating the database......../') - - df = pd.DataFrame(db_copy_list) - df['name'] = df['name'].str.strip() - df = df.fillna('') - db_df_list = df.to_dict('records') + print('Correcting the component/release names and updating the database......../') + df = pd.DataFrame(db_copy_list) + df['name'] = df['name'].str.strip() + df = df.fillna('') + db_df_list = df.to_dict('records') + if not DRY_RUN: #Update call db.update(db_df_list) -def generateLogFile(db_copy_list): + return db_df_list + +def generateLogFile(header, db_copy_list): print('Generating log file......../') - logFile = open('_050_remove_whitespace_component_name.log', 'w') - json.dump(db_copy_list, logFile, indent = 4, sort_keys = True) + with open("_053_remove_whitespace_component_name.log", "a") as f: + f.write(header) + keys_to_extract = ['_id', 'name'] + result = [{key: d[key] for key in keys_to_extract if key in d} for d in db_copy_list] + logFile = open('_053_remove_whitespace_component_name.log', 'a') + json.dump(result, logFile, indent = 4, sort_keys = True) logFile.close() +def checkDuplicate(db, db_copy_list): + df = pd.DataFrame(db_copy_list) + df['name'] = df['name'].str.strip() + df = df.fillna('') + results = [] + query = {"selector": {"type": {"$eq": "component"},"$or": [{"name": name} for name in df['name']]},"limit": 99999} + results = db.find(query) + return list(results) + +def checkDuplicateReleases(db, db_copy_list): + print('Checking duplicate releases......./') + df = pd.DataFrame(db_copy_list) + df['name'] = df['name'].str.strip() + df = df.fillna('') + existing_release_list = [] + query = {"selector": {"type": {"$eq": "release"},"$or": [{"name": name} for name in df['name']]},"limit": 99999} + existing_release_list = db.find(query) + db_copy_list = df.to_dict('records') + dup_rel_list = [] + for rel in list(existing_release_list): + for x in db_copy_list: + if(rel['name']==x['name'] and rel['componentId']==x['componentId'] and rel['version']==x['version']): + dup_rel_list.append(x) + return dup_rel_list + +def linkReleaseToComponent(db, db_corrected_comp_list, db_existing_comp_list): + + print('######################') + print('Linking Releases to Components........./') + + db_duplicate_corrected_comp_list = filter(lambda x: x['name'] in [d['name'] for d in db_existing_comp_list], db_corrected_comp_list) + + db_upd_comp_list = [] + db_upd_releases_list = [] + db_del_comp_Ids = [] + for x in list(db_duplicate_corrected_comp_list): + for comp in db_existing_comp_list: + if(x['name'] == comp['name']): + db_del_comp_Ids.append(x['_id']) + for i in x["releaseIds"]: + query = {"selector": {"type": {"$eq": "release"}, "_id": { "$eq": i }},"limit": 99999} + relation = db.find(query) + relation_list = list(relation) + print(relation_list[0]["componentId"]) + relation_list[0]["componentId"] = comp['_id'] + print(relation_list[0]["componentId"]) + if("releaseIds" not in comp): + comp["releaseIds"] = [] + comp["releaseIds"].append(i) + db_upd_releases_list.append(relation_list[0]) + db_upd_comp_list.append(comp) + + db_dup_release_list = checkDuplicateReleases(db, db_upd_releases_list) + + #Generate log file + header = (f'Duplicate releases that will be merged - total count : {len(db_dup_release_list)}') + generateLogFile(header, db_dup_release_list) + + #Generate log file + header = (f'Releases that will be merged - total count : {len(db_upd_releases_list)}') + generateLogFile(header, db_upd_releases_list) + + print('Updating Linkage of Components and Releases............/') + if not DRY_RUN: + db.update(db_upd_comp_list) + db.update(db_upd_releases_list) + + print(db_del_comp_Ids) + return db_del_comp_Ids + +def deleteDuplicateComponents(db, db_del_comp_Ids): + if not DRY_RUN: + #Generate log file + header = (f'Duplicate components that are getting deleted {len(db_del_comp_Ids)}') + generateLogFile(header, db_del_comp_Ids) + print('Deleting duplicate components........../') + for id in db_del_comp_Ids : + del db[id] + def run(): - + #DB connection db = dbConnection() - - #Query to fetch the component names - db_copy_list = queryExecution(db) + #Query to fetch the component names with whitespace + db_comp_list = queryExecution(db,'component') #Generate log file - generateLogFile(db_copy_list) + header = (f'Components with whitespaces - total count : {len(db_comp_list)}') + generateLogFile(header, db_comp_list) - #Converting document data to dataframe and removing the whitespaces - - if len(db_copy_list)!=0: - updateDB(db,db_copy_list) + #Query to fetch the release names with whitespaces + db_rel_list = queryExecution(db,'release') + #Generate log file + header = (f'Releases with whitespaces - total count : {len(db_rel_list)}') + generateLogFile(header, db_rel_list) + + #check Duplicate + if len(db_comp_list)!=0: + db_existing_comp_list = checkDuplicate(db,db_comp_list) + #Generate log file + header = (f'Existing components with the same name - total count : {len(db_existing_comp_list)}') + generateLogFile(header, db_existing_comp_list) + + #Converting document data to dataframe and removing the whitespace of components + if len(db_comp_list)!=0: + db_corrected_comp_list = updateDB(db,db_comp_list) + #Generate log file + header = (f'Corrected components - total count : {len(db_corrected_comp_list)}') + generateLogFile(header, db_corrected_comp_list) else: print('No Records found!') + + #Converting document data to dataframe and removing the whitespace of releases + if len(db_rel_list)!=0: + db_corrected_rel_list = updateDB(db,db_rel_list) + #Generate log file + header = (f'Corrected components - total count : {len(db_corrected_rel_list)}') + generateLogFile(header, db_corrected_rel_list) + else: + print('No Records found!') + + #link Releases of duplicate components to the exisiting components + if len(db_corrected_comp_list)!=0: + db_del_comp_Ids = linkReleaseToComponent(db, db_corrected_comp_list, db_existing_comp_list) + #delete duplicate components + if len(db_del_comp_Ids)!=0: + deleteDuplicateComponents(db, db_del_comp_Ids) + print('Execution completed....') if __name__ == '__main__': @@ -89,6 +205,3 @@ def run(): except Exception as e: print('Exception message ',e) - - - From aecc1914136c703482ae19302a0bd11bff0f21af Mon Sep 17 00:00:00 2001 From: rudra-superrr Date: Mon, 20 Mar 2023 15:07:26 +0530 Subject: [PATCH 02/20] fix(rest): endpoint api/projects does not return all projects Signed-off-by: rudra-superrr --- .../project/ProjectController.java | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) 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 464329e57e..14cffe142e 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 @@ -247,8 +247,7 @@ private ResponseEntity>> getProjectResponse Project embeddedProject = restControllerHelper.convertToEmbeddedProject(p); embeddedProjectResource = EntityModel.of(embeddedProject); } else { - embeddedProjectResource = createHalProjectResourceWithAllDetails(p, sw360User, mapOfProjects, - !isSearchByName); + embeddedProjectResource = createHalProjectResourceWithAllDetails(p, sw360User); if (embeddedProjectResource == null) { return; } @@ -1069,12 +1068,7 @@ private RequestStatus addOrPatchReleasesToProject(String id, Object releasesInRe return projectService.updateProject(project, sw360User); } - private HalResource createHalProjectResourceWithAllDetails(Project sw360Project, User sw360User, - Map mapOfProjects, boolean isAllAccessibleProjectFetched) { - Map linkedProjects = sw360Project.getLinkedProjects(); - if (!isLinkedProjectsVisible(linkedProjects, sw360User, mapOfProjects, isAllAccessibleProjectFetched)) { - return null; - } + private HalResource createHalProjectResourceWithAllDetails(Project sw360Project, User sw360User) { HalResource halProject = new HalResource<>(sw360Project); halProject.addEmbeddedResource("createdBy", sw360Project.getCreatedBy()); @@ -1098,28 +1092,6 @@ private HalResource createHalProjectResourceWithAllDetails(Project sw36 return halProject; } - private boolean isLinkedProjectsVisible(Map linkedProjects, User sw360User, - Map mapOfProjects, boolean isAllAccessibleProjectFetched) { - if (isAllAccessibleProjectFetched && !CommonUtils.isNullOrEmptyMap(linkedProjects)) { - for (String linkedProjectId : linkedProjects.keySet()) { - if (!mapOfProjects.containsKey(linkedProjectId)) { - return false; - } - } - } else if (!CommonUtils.isNullOrEmptyMap(linkedProjects)) { - for (String linkedProjectId : linkedProjects.keySet()) { - if (!mapOfProjects.containsKey(linkedProjectId)) { - try { - projectService.getProjectForUserById(linkedProjectId, sw360User); - } catch (TException exp) { - return false; - } - } - } - } - return true; - } - private Project convertToProject(Map requestBody) { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); From cd4293f971aae0ca4b2bfa38a195e2de4f044eba Mon Sep 17 00:00:00 2001 From: Tien Le Date: Wed, 22 Mar 2023 14:43:34 +0700 Subject: [PATCH 03/20] fix(api): deletion project returns 500 error and API doc of link release to release makes ambiguous Signed-off-by: Tien Le --- .../resourceserver/project/ProjectController.java | 12 ++++++++++-- .../resourceserver/project/Sw360ProjectService.java | 9 ++------- .../resourceserver/restdocs/ProjectSpecTest.java | 2 +- .../resourceserver/restdocs/ReleaseSpecTest.java | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) 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 464329e57e..061ca62bc2 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 @@ -328,8 +328,16 @@ public ResponseEntity> getProject( @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.DELETE) public ResponseEntity deleteProject(@PathVariable("id") String id) throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - projectService.deleteProject(id, sw360User); - return new ResponseEntity<>(HttpStatus.OK); + RequestStatus requestStatus = projectService.deleteProject(id, sw360User); + if(requestStatus == RequestStatus.SUCCESS) { + return new ResponseEntity<>(HttpStatus.OK); + } else if(requestStatus == RequestStatus.IN_USE) { + return new ResponseEntity<>(HttpStatus.CONFLICT); + } else if (requestStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST,HttpStatus.ACCEPTED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } } @PreAuthorize("hasAuthority('WRITE')") 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 aa34b2ac89..b26acf3fa5 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 @@ -203,14 +203,9 @@ public RequestStatus updateProject(Project project, User sw360User) throws TExce return requestStatus; } - public void deleteProject(String projectId, User sw360User) throws TException { + public RequestStatus deleteProject(String projectId, User sw360User) throws TException { ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); - RequestStatus requestStatus = sw360ProjectClient.deleteProject(projectId, sw360User); - if (requestStatus == RequestStatus.IN_USE) { - throw new HttpMessageNotReadableException("Unable to delete project. Project is in Use"); - } else if (requestStatus != RequestStatus.SUCCESS) { - throw new RuntimeException("sw360 project with id '" + projectId + " cannot be deleted."); - } + return sw360ProjectClient.deleteProject(projectId, sw360User); } public void deleteAllProjects(User sw360User) throws TException { 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 ce33b8f922..52c62cf841 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 @@ -157,7 +157,6 @@ public void before() throws TException, IOException { given(this.attachmentServiceMock.getResourcesFromList(any())).willReturn(CollectionModel.of(attachmentResources)); given(this.attachmentServiceMock.uploadAttachment(any(), any(), any())).willReturn(attachment); given(this.attachmentServiceMock.updateAttachment(any(), any(), any(), any())).willReturn(att2); - Mockito.doNothing().when(projectServiceMock).deleteProject(any(), any()); Mockito.doNothing().when(projectServiceMock).copyLinkedObligationsForClonedProject(any(), any(), any()); @@ -298,6 +297,7 @@ public void before() throws TException, IOException { given(this.projectServiceMock.refineSearch(any(), any())).willReturn(projectListByName); given(this.projectServiceMock.getReleaseIds(eq(project.getId()), any(), eq("false"))).willReturn(releaseIds); given(this.projectServiceMock.getReleaseIds(eq(project.getId()), any(), eq("true"))).willReturn(releaseIdsTransitive); + given(this.projectServiceMock.deleteProject(eq(project.getId()), any())).willReturn(RequestStatus.SUCCESS); given(this.projectServiceMock.updateProjectReleaseRelationship(any(), any(), any())).willReturn(projectReleaseRelationshipResponseBody); given(this.projectServiceMock.convertToEmbeddedWithExternalIds(eq(project))).willReturn( new Project("Emerald Web") diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index 253087d399..2f9d904d87 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -203,7 +203,7 @@ public void before() throws TException, IOException { Map release2ExternalIds = new HashMap<>(); release2ExternalIds.put("mainline-id-component", "4876"); release2ExternalIds.put("ws-component-id", "[\"589211\",\"987135\"]"); - release2.setId("3765276512"); + release2.setId("6868686868"); release2.setName("Angular"); release2.setCpeid("cpe:/a:Google:Angular:2.3.1:"); release2.setReleaseDate("2016-12-15"); From c923fa8475c2f8b039a4b82f33494c07ad96cd8d Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Mon, 3 Apr 2023 16:13:20 +0700 Subject: [PATCH 04/20] fix(component_gui): Can not load component detail page with long additional data text Signed-off-by: hoangnt2 --- .../resources/js/components/includes/releases/regexjs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/releases/regexjs.js b/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/releases/regexjs.js index b16383c824..33174852ba 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/releases/regexjs.js +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/releases/regexjs.js @@ -11,7 +11,7 @@ define('components/includes/releases/regexjs', ['jquery'] , function($) { function regex(key,value) { - let regexEmail = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + let regexEmail = /^\w+([\.-]\w+)*@\w+([\.-]\w+)*(\.\w{2,3})+$/; let regexUrl = /^(https?|chrome):\/\/[^\s$.?#].[^\s]*$/g; let content = ''; if (value.match(regexEmail)) { From 56096f24a78527f7f7e3aa9c476e181b388af806 Mon Sep 17 00:00:00 2001 From: afsahsyeda Date: Thu, 6 Apr 2023 13:37:23 +0530 Subject: [PATCH 05/20] feat(ProjectUI):ExternalIds and Additional Data fields in Export Excel Signed-off-by: afsahsyeda --- libraries/datahandler/src/main/thrift/projects.thrift | 9 +++++---- .../java/org/eclipse/sw360/exporter/ProjectExporter.java | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/datahandler/src/main/thrift/projects.thrift b/libraries/datahandler/src/main/thrift/projects.thrift index 8e92f12209..5e8ca5d5fb 100644 --- a/libraries/datahandler/src/main/thrift/projects.thrift +++ b/libraries/datahandler/src/main/thrift/projects.thrift @@ -94,10 +94,6 @@ struct Project { 6: optional string version, 7: optional string domain, - // information from external data sources - 9: optional map externalIds, - 300: optional map additionalData, - // Additional informations 10: optional set attachments, 11: optional string createdOn, // Creation date YYYY-MM-dd @@ -134,6 +130,11 @@ struct Project { 44: optional string deliveryStart, 45: optional string phaseOutSince, 46: optional bool enableSvm, // flag for enabling Security Vulnerability Monitoring + + // information from external data sources + 9: optional map externalIds, + 300: optional map additionalData, + 49: optional bool considerReleasesFromExternalList, // Consider list of releases from existing external list 47: optional string licenseInfoHeaderText; 48: optional bool enableVulnerabilitiesDisplay, // flag for enabling displaying vulnerabilities in project view diff --git a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java index cc05515a12..035af033b6 100644 --- a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java +++ b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java @@ -19,7 +19,6 @@ import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.users.User; -import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.exporter.helper.ExporterHelper; import org.eclipse.sw360.exporter.helper.ProjectHelper; import org.eclipse.sw360.exporter.helper.ReleaseHelper; @@ -46,7 +45,6 @@ public class ProjectExporter extends ExcelExporter { nameToDisplayName.put(TAG.getFieldName(), "project tag"); nameToDisplayName.put(BUSINESS_UNIT.getFieldName(), "group"); nameToDisplayName.put(RELEASE_CLEARING_STATE_SUMMARY.getFieldName(), "release clearing state summary"); - nameToDisplayName.put(EXTERNAL_IDS.getFieldName(), "external IDs"); nameToDisplayName.put(VISBILITY.getFieldName(), "visibility"); nameToDisplayName.put(PROJECT_TYPE.getFieldName(), "project type"); nameToDisplayName.put(LINKED_PROJECTS.getFieldName(), "linked projects with relationship"); @@ -65,6 +63,8 @@ public class ProjectExporter extends ExcelExporter { nameToDisplayName.put(SECURITY_RESPONSIBLES.getFieldName(), "security responsibles"); nameToDisplayName.put(CREATED_ON.getFieldName(), "created on"); nameToDisplayName.put(ENABLE_SVM.getFieldName(), "enable svm"); + nameToDisplayName.put(EXTERNAL_IDS.getFieldName(), "external IDs"); + nameToDisplayName.put(ADDITIONAL_DATA.getFieldName(), "additional data"); nameToDisplayName.put(VISBILITY.getFieldName(), "Project visibility"); } @@ -80,6 +80,8 @@ public class ProjectExporter extends ExcelExporter { .add(SECURITY_RESPONSIBLES) .add(CREATED_ON) .add(ENABLE_SVM) + .add(EXTERNAL_IDS) + .add(ADDITIONAL_DATA) .add(VISBILITY) .build(); From 72920799792c328a741e904a384f37c3910a13ba Mon Sep 17 00:00:00 2001 From: afsahsyeda Date: Thu, 6 Apr 2023 11:36:16 +0530 Subject: [PATCH 06/20] feat(EditCR): Admin will be able to reassign/edit the Requesting User of CR Signed-off-by: afsahsyeda --- .../moderation/db/ModerationDatabaseHandler.java | 13 +++++++++++++ .../portlets/moderation/ModerationPortlet.java | 2 ++ .../moderation/ModerationPortletUtils.java | 8 ++++++++ .../html/moderation/clearing/clearingRequest.jsp | 16 ++++++++++++++-- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java index d6ca79b5fc..ba42ae5098 100644 --- a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java +++ b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java @@ -207,6 +207,10 @@ public RequestStatus updateClearingRequest(ClearingRequest request, User user, S request.unsetPriority(); } ClearingRequest currentRequest = getClearingRequestByIdForEdit(request.getId(), user); + + if (request.equals(currentRequest)) { + return RequestStatus.SUCCESS; + } StringBuilder commentText = new StringBuilder("Clearing Request is updated: "); if (!currentRequest.getClearingState().equals(request.getClearingState())) { if (ClearingRequestState.CLOSED.equals(currentRequest.getClearingState()) @@ -217,6 +221,14 @@ public RequestStatus updateClearingRequest(ClearingRequest request, User user, S .append(ThriftEnumUtils.enumToString(currentRequest.getClearingState())).append(" to ") .append(ThriftEnumUtils.enumToString(request.getClearingState())).append(""); } + String oldRequestingUser = CommonUtils.nullToEmptyString(currentRequest.getRequestingUser()); + String newRequestingUser = CommonUtils.nullToEmptyString(request.getRequestingUser()); + if (!oldRequestingUser.equals(newRequestingUser)) { + commentText = commentText.append("\n\tRequesting User changed from: ") + .append(StringUtils.defaultIfBlank(oldRequestingUser, "NULL")).append(" to ") + .append(StringUtils.defaultIfBlank(newRequestingUser, "NULL")).append(""); + } + String oldAgreedClDate = CommonUtils.nullToEmptyString(currentRequest.getAgreedClearingDate()); String newAgreedClDate = CommonUtils.nullToEmptyString(request.getAgreedClearingDate()); if (!oldAgreedClDate.equals(newAgreedClDate)) { @@ -254,6 +266,7 @@ public RequestStatus updateClearingRequest(ClearingRequest request, User user, S clearingRequestRepository.update(request); projectDatabaseHandler.sendEmailForClearingRequestUpdate(request, projectUrl, user); return RequestStatus.SUCCESS; + } catch (SW360Exception e) { log.error("Failed to update clearing request: " + request.getId(), e); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortlet.java index 05dc03a3ff..c832f35532 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortlet.java @@ -460,6 +460,8 @@ private void renderClearingRequest(RenderRequest request, RenderResponse respons final String clearingId = request.getParameter(CLEARING_REQUEST_ID); final User user = UserCacheHolder.getUserFromRequest(request); + request.setAttribute(IS_USER_ADMIN, PermissionUtils.isUserAtLeast(UserGroup.SW360_ADMIN, user) ? "Yes" : "No"); + if (CommonUtils.isNullEmptyOrWhitespace(clearingId)) { throw new PortletException("Clearing request ID not set!"); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortletUtils.java index cff4e1e363..33c0570a55 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortletUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/moderation/ModerationPortletUtils.java @@ -104,6 +104,14 @@ public static AddDocumentRequestSummary updateClearingRequest(PortletRequest req String isClearingExpertEdit = request.getParameter(PortalConstants.IS_CLEARING_EXPERT); ModerationService.Iface client = new ThriftClients().makeModerationClient(); ClearingRequest clearingRequest = client.getClearingRequestByIdForEdit(id, user); + + String requestingUser = request.getParameter(ClearingRequest._Fields.REQUESTING_USER.toString()); + if (CommonUtils.isNullEmptyOrWhitespace(requestingUser)) { + log.warn("Invalid requesting user email: " + requestingUser + " is entered, by user: "+ user.getEmail()); + return requestSummary.setMessage("Invalid requesting user email"); + } + clearingRequest.setRequestingUser(requestingUser); + String clearingTeam = request.getParameter(ClearingRequest._Fields.CLEARING_TEAM.toString()); if (CommonUtils.isNullEmptyOrWhitespace(clearingTeam)) { log.warn("Invalid clearingTeam email: " + clearingTeam + " is entered, by user: "+ user.getEmail()); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/moderation/clearing/clearingRequest.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/moderation/clearing/clearingRequest.jsp index ce977bb2de..71e1585102 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/moderation/clearing/clearingRequest.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/moderation/clearing/clearingRequest.jsp @@ -31,7 +31,7 @@ - + @@ -137,7 +137,19 @@ - + + + + +
+ +
+
+ + + +
+ From fdbc8c360c050069023de276ff4beeeb638ac2de Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Wed, 12 Apr 2023 15:39:00 +0200 Subject: [PATCH 07/20] fix(docker): Adjust config defaults - Liferay Company wrong id was preventing rest to proper authenticate. - CORS for Docker base dev container changed to deny to allow all connections. - Extended timeout for disconnections. #0 minutes was really short for use during development. Signed-off-by: Helio Chissini de Castro --- scripts/docker-config/etc_sw360/authorization/application.yml | 2 +- scripts/docker-config/etc_sw360/rest/application.yml | 2 +- scripts/docker-config/etc_sw360/sw360.properties | 2 +- scripts/docker-config/portal-ext.properties | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/docker-config/etc_sw360/authorization/application.yml b/scripts/docker-config/etc_sw360/authorization/application.yml index 0f432ff695..d940a649d7 100644 --- a/scripts/docker-config/etc_sw360/authorization/application.yml +++ b/scripts/docker-config/etc_sw360/authorization/application.yml @@ -36,7 +36,7 @@ sw360: sw360-liferay-company-id: ${SW360_LIFERAY_COMPANY_ID:20101} # Allowed origins that should be set in the header cors: - allowed-origin: ${SW360_CORS_ALLOWED_ORIGIN:#{null}} + allowed-origin: ${SW360_CORS_ALLOWED_ORIGIN:*} security: # Configuration for enabling authorization via headers, e.g. when using SSO diff --git a/scripts/docker-config/etc_sw360/rest/application.yml b/scripts/docker-config/etc_sw360/rest/application.yml index 0abfd466bf..792d46c836 100644 --- a/scripts/docker-config/etc_sw360/rest/application.yml +++ b/scripts/docker-config/etc_sw360/rest/application.yml @@ -58,4 +58,4 @@ sw360: test-user-password: sw360-password couchdb-url: ${SW360_COUCHDB_URL:http://couchdb:5984} cors: - allowed-origin: ${SW360_CORS_ALLOWED_ORIGIN:#{null}} + allowed-origin: ${SW360_CORS_ALLOWED_ORIGIN:*} diff --git a/scripts/docker-config/etc_sw360/sw360.properties b/scripts/docker-config/etc_sw360/sw360.properties index 6cb838c869..8f9598040c 100644 --- a/scripts/docker-config/etc_sw360/sw360.properties +++ b/scripts/docker-config/etc_sw360/sw360.properties @@ -10,4 +10,4 @@ rest.apitoken.generator.enable = true custom.welcome.page.guideline = true -sw360.liferay.company.id=20098 +sw360.liferay.company.id=20099 diff --git a/scripts/docker-config/portal-ext.properties b/scripts/docker-config/portal-ext.properties index 8d26f796d6..1bc076646c 100644 --- a/scripts/docker-config/portal-ext.properties +++ b/scripts/docker-config/portal-ext.properties @@ -95,7 +95,7 @@ company.security.strangers.verify=false # *********************************** # Specify the number of minutes before a session expires. This value is # always overridden by the value set in web.xml. -session.timeout=30 +session.timeout=90 # Specify the number of minutes before a warning is sent to the user # informing the user of the session expiration. Specify 0 to disable any From 4f171a65971a226216dae423de3f265010119600 Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Fri, 7 Apr 2023 16:36:14 +0530 Subject: [PATCH 08/20] feat(rest): optimize fetch project While fetching the projects without filter on `/projects` endpoint, optimize the call to `getAccessibleProjectsSummary()`. Works on #1899 Signed-off-by: Gaurav Mishra --- .../resourcelists/ResourceListController.java | 8 ++++- .../core/RestControllerHelper.java | 12 +++++++ .../project/ProjectController.java | 25 ++++++++----- .../project/Sw360ProjectService.java | 36 ++++++++++--------- .../integration/ProjectTest.java | 2 +- .../restdocs/ProjectSpecTest.java | 2 +- 6 files changed, 57 insertions(+), 28 deletions(-) diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceListController.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceListController.java index 8e078f4f0b..3286a88724 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceListController.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceListController.java @@ -11,7 +11,6 @@ package org.eclipse.sw360.datahandler.resourcelists; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -40,6 +39,13 @@ public PaginationResult applyPagingToList(List resources, PaginationOption return new PaginationResult<>(sortedResources.subList(fromIndex, toIndex), sortedResources.size(), paginationOptions); } + public PaginationResult getPaginationResultFromPaginatedList(List resources, + PaginationOptions paginationOptions, + int totalCount) { + return new PaginationResult<>(this.sortList(resources, paginationOptions.getSortComparator()), + totalCount, paginationOptions); + } + private List sortList(List resources, Comparator comparator) { if(comparator == null) { return resources; 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 215e7d6e0a..cd65d09b9c 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 @@ -139,6 +139,18 @@ public PaginationResult createPaginationResult(HttpServletRequest request, Pa return paginationResult; } + public PaginationResult paginationResultFromPaginatedList(HttpServletRequest request, Pageable pageable, + List resources, String resourceType, int totalCount) + throws ResourceClassNotFoundException { + if (!requestContainsPaging(request)) { + request.setAttribute(PAGINATION_PARAM_PAGE, pageable.getPageNumber()); + request.setAttribute(PAGINATION_PARAM_PAGE_ENTRIES, pageable.getPageSize()); + } + PaginationOptions paginationOptions = paginationOptionsFromPageable(pageable, resourceType); + return resourceListController.getPaginationResultFromPaginatedList(resources, + paginationOptions, totalCount); + } + private boolean requestContainsPaging(HttpServletRequest request) { return request.getParameterMap().containsKey(PAGINATION_PARAM_PAGE) || request.getParameterMap().containsKey(PAGINATION_PARAM_PAGE_ENTRIES); } 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 b16324c842..e39af561eb 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 @@ -190,6 +190,7 @@ public ResponseEntity>> getProjectsForUser( boolean isSearchByTag = CommonUtils.isNotNullEmptyOrWhitespace(tag); boolean isSearchByType = CommonUtils.isNotNullEmptyOrWhitespace(projectType); boolean isSearchByGroup = CommonUtils.isNotNullEmptyOrWhitespace(group); + boolean isNoFilter = false; List sw360Projects = new ArrayList<>(); Map> filterMap = new HashMap<>(); if (luceneSearch) { @@ -224,21 +225,29 @@ public ResponseEntity>> getProjectsForUser( } else if (isSearchByType) { sw360Projects.addAll(projectService.searchProjectByType(projectType, sw360User)); } else { - sw360Projects.addAll(projectService.getProjectsForUser(sw360User)); + sw360Projects.addAll(projectService.getProjectsForUser(sw360User, pageable)); + isNoFilter = true; } } return getProjectResponse(pageable, projectType, group, tag, allDetails, luceneSearch, request, sw360User, - mapOfProjects, isSearchByName, sw360Projects); + mapOfProjects, isSearchByName, sw360Projects, isNoFilter); } @NotNull private ResponseEntity>> getProjectResponse(Pageable pageable, - String projectType, String group, String tag, boolean allDetails, boolean luceneSearch, - HttpServletRequest request, User sw360User, Map mapOfProjects, boolean isSearchByName, - List sw360Projects) throws ResourceClassNotFoundException, PaginationParameterException, URISyntaxException { + String projectType, String group, String tag, boolean allDetails, boolean luceneSearch, + HttpServletRequest request, User sw360User, Map mapOfProjects, boolean isSearchByName, + List sw360Projects, boolean isNoFilter) throws ResourceClassNotFoundException, PaginationParameterException, URISyntaxException, TException { sw360Projects.stream().forEach(prj -> mapOfProjects.put(prj.getId(), prj)); - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, - sw360Projects, SW360Constants.TYPE_PROJECT); + PaginationResult paginationResult; + if (isNoFilter) { + int totalCount = projectService.getMyAccessibleProjectCounts(sw360User); + paginationResult = restControllerHelper.paginationResultFromPaginatedList(request, pageable, + sw360Projects, SW360Constants.TYPE_PROJECT, totalCount); + } else { + paginationResult = restControllerHelper.createPaginationResult(request, pageable, + sw360Projects, SW360Constants.TYPE_PROJECT); + } List> projectResources = new ArrayList<>(); Consumer consumer = p -> { @@ -313,7 +322,7 @@ public ResponseEntity>> getProjectsFiltered sw360Projects = projectService.getWithFilledClearingStatus(sw360Projects, clearingState); return getProjectResponse(pageable, null, null, null, allDetails, true, request, sw360User, - mapOfProjects, true, sw360Projects); + mapOfProjects, true, sw360Projects, false); } @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.GET) 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 9e800160b2..b27f3fa3f3 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 @@ -48,6 +48,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.Link; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -95,24 +96,14 @@ public class Sw360ProjectService implements AwareOfRestServices { public static final ExecutorService releaseExecutor = Executors.newFixedThreadPool(10); - public Set getProjectsForUser(User sw360User) throws TException { + public Set getProjectsForUser(User sw360User, Pageable pageable) throws TException { ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); - int total = sw360ProjectClient.getMyAccessibleProjectCounts(sw360User); - PaginationData pageData = new PaginationData(); - pageData.setAscending(true); - Map> pageDtToProjects; - Set projects = new HashSet<>(); - int displayStart = 0; - int rowsPerPage = 500; - while (0 < total) { - pageData.setDisplayStart(displayStart); - pageData.setRowsPerPage(rowsPerPage); - displayStart = displayStart + rowsPerPage; - pageDtToProjects = sw360ProjectClient.getAccessibleProjectsSummaryWithPagination(sw360User, pageData); - projects.addAll(pageDtToProjects.entrySet().iterator().next().getValue()); - total = total - rowsPerPage; - } - return projects; + PaginationData pageData = new PaginationData() + .setDisplayStart((int) pageable.getOffset()) + .setRowsPerPage(pageable.getPageSize()) + .setSortColumnNumber(0); + Map> pageDtToProjects = sw360ProjectClient.getAccessibleProjectsSummaryWithPagination(sw360User, pageData); + return new HashSet<>(pageDtToProjects.entrySet().iterator().next().getValue()); } public Project getProjectForUserById(String projectId, User sw360User) throws TException { @@ -485,4 +476,15 @@ public List getMyProjects(User user, Map userRoles) th ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); return sw360ProjectClient.getMyProjects(user, userRoles); } + + /** + * Get count of projects accessible by given user. + * @param sw360User User to get the count for. + * @return Total count of projects accessible by user. + * @throws TException + */ + public int getMyAccessibleProjectCounts(User sw360User) throws TException { + ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); + return sw360ProjectClient.getMyAccessibleProjectCounts(sw360User); + } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/ProjectTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/ProjectTest.java index 5a84b80331..26e319d03f 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/ProjectTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/integration/ProjectTest.java @@ -53,7 +53,7 @@ public void before() throws TException { project.setDescription("Project description"); projectList.add(project); - given(this.projectServiceMock.getProjectsForUser(any())).willReturn(projectList); + given(this.projectServiceMock.getProjectsForUser(any(), any())).willReturn(projectList); User user = new User(); user.setId("123456789"); 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 98bd3db204..6ade125c00 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 @@ -286,7 +286,7 @@ public void before() throws TException, IOException { Set releaseIds = new HashSet<>(Collections.singletonList("3765276512")); Set releaseIdsTransitive = new HashSet<>(Arrays.asList("3765276512", "5578999")); - given(this.projectServiceMock.getProjectsForUser(any())).willReturn(projectList); + given(this.projectServiceMock.getProjectsForUser(any(), any())).willReturn(projectList); given(this.projectServiceMock.getProjectForUserById(eq(project.getId()), any())).willReturn(project); given(this.projectServiceMock.getProjectForUserById(eq(project2.getId()), any())).willReturn(project2); given(this.projectServiceMock.getProjectForUserById(eq(projectForAtt.getId()), any())).willReturn(projectForAtt); From 2e0732a2bc29c61411f241977f129dfc3731ebdd Mon Sep 17 00:00:00 2001 From: Smruti Prakash Sahoo Date: Thu, 2 Feb 2023 13:55:08 +0530 Subject: [PATCH 09/20] feat(rest): Added basic username and password based authentication Signed-off-by: Smruti Prakash Sahoo --- .../org/eclipse/sw360/users/UserHandler.java | 7 +-- .../sw360/users/db/UserDatabaseHandler.java | 21 +++++++- .../eclipse/sw360/users/UserHandlerTest.java | 8 ++- .../datahandler/src/main/thrift/users.thrift | 4 +- .../Sw360GrantedAuthoritiesCalculator.java | 2 +- .../src/docs/asciidoc/api-guide.adoc | 3 ++ .../src/docs/asciidoc/users.adoc | 13 ++++- .../resourceserver/Sw360ResourceServer.java | 7 +++ .../core/JacksonCustomizations.java | 6 ++- .../core/RestControllerHelper.java | 9 +++- .../filter/EndpointsFilter.java | 43 ++++++++++++++++ .../security/ResourceServerConfiguration.java | 24 ++++++++- .../basic/Sw360CustomUserDetailsService.java | 35 +++++++++++++ .../Sw360GrantedAuthoritiesCalculator.java | 37 ++++++++++++++ .../security/basic/Sw360GrantedAuthority.java | 17 +++++++ .../basic/Sw360UserDetailsProvider.java | 51 +++++++++++++++++++ .../resourceserver/user/Sw360UserService.java | 28 ++++++++++ .../resourceserver/user/UserController.java | 28 ++++++++++ .../src/main/resources/application.yml | 4 ++ .../resourceserver/restdocs/UserSpecTest.java | 51 +++++++++++++++++-- 20 files changed, 380 insertions(+), 18 deletions(-) create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/filter/EndpointsFilter.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360CustomUserDetailsService.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthoritiesCalculator.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthority.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360UserDetailsProvider.java diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java index 49d687c738..2bd717d93e 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.common.DatabaseSettings; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -30,6 +31,7 @@ import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotEmpty; import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertUser; /** * Implementation of the Thrift service @@ -101,9 +103,8 @@ public List getAllUsers() { } @Override - public RequestStatus addUser(User user) throws TException { - assertNotNull(user); - assertNotNull(user.getEmail()); + public AddDocumentRequestSummary addUser(User user) throws TException { + assertUser(user); return db.addUser(user); } diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java index 197be5aa3d..4e972bda94 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java @@ -10,10 +10,13 @@ package org.eclipse.sw360.users.db; import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; +import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.DatabaseSettings; import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.db.UserRepository; import org.eclipse.sw360.datahandler.db.UserSearchHandler; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.SW360Exception; @@ -39,6 +42,8 @@ */ public class UserDatabaseHandler { + private static final String LAST_NAME_IS_MANDATORY = "Last Name is mandatory"; + private static final String GIVEN_NAME_IS_MANDATORY = "Given Name is mandatory"; /** * Connection to the couchDB database */ @@ -76,12 +81,24 @@ private void prepareUser(User user) throws SW360Exception { ThriftValidate.prepareUser(user); } - public RequestStatus addUser(User user) throws SW360Exception { + public AddDocumentRequestSummary addUser(User user) throws SW360Exception { prepareUser(user); + AddDocumentRequestSummary addDocReqSummarry = new AddDocumentRequestSummary(); + if (CommonUtils.isNullEmptyOrWhitespace(user.getGivenname())) { + return addDocReqSummarry.setMessage(GIVEN_NAME_IS_MANDATORY).setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT); + } else if (CommonUtils.isNullEmptyOrWhitespace(user.getLastname())) { + return addDocReqSummarry.setMessage(LAST_NAME_IS_MANDATORY).setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT); + } + + User existingUserInDB = getByEmail(user.getEmail()); + if (null != existingUserInDB) { + return addDocReqSummarry.setId(existingUserInDB.getId()) + .setRequestStatus(AddDocumentRequestStatus.DUPLICATE); + } // Add to database db.add(user); - return RequestStatus.SUCCESS; + return addDocReqSummarry.setId(user.getId()).setRequestStatus(AddDocumentRequestStatus.SUCCESS); } public RequestStatus updateUser(User user) throws SW360Exception { diff --git a/backend/src/src-users/src/test/java/org/eclipse/sw360/users/UserHandlerTest.java b/backend/src/src-users/src/test/java/org/eclipse/sw360/users/UserHandlerTest.java index 23005f91bc..af3fb2284a 100644 --- a/backend/src/src-users/src/test/java/org/eclipse/sw360/users/UserHandlerTest.java +++ b/backend/src/src-users/src/test/java/org/eclipse/sw360/users/UserHandlerTest.java @@ -21,6 +21,10 @@ public class UserHandlerTest { + private static final String DUMMY_LASTNAME = "Dummy Lastname"; + + private static final String DUMMY_GIVENNAME = "Dummy Givenname"; + private static final String dbName = DatabaseSettingsTest.COUCH_DB_USERS; private static final String DUMMY_EMAIL_ADDRESS_1 = "dummy.user1@dummy.domain.tld"; @@ -47,7 +51,7 @@ public void tearDown() throws Exception { @Test public void testAddUser() throws Exception { - User userWithComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_1).setCommentMadeDuringModerationRequest(DUMMY_COMMENT); + User userWithComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_1).setCommentMadeDuringModerationRequest(DUMMY_COMMENT).setGivenname(DUMMY_GIVENNAME).setLastname(DUMMY_LASTNAME).setDepartment(DUMMY_DEPARTMENT); handler.addUser(userWithComment); @@ -58,7 +62,7 @@ public void testAddUser() throws Exception { @Test public void testUpdateUser() throws Exception { - User userWithoutComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_2); + User userWithoutComment = new User().setEmail(DUMMY_EMAIL_ADDRESS_2).setGivenname(DUMMY_GIVENNAME).setLastname(DUMMY_LASTNAME).setDepartment(DUMMY_DEPARTMENT); handler.addUser(userWithoutComment); // does not contain a comment diff --git a/libraries/datahandler/src/main/thrift/users.thrift b/libraries/datahandler/src/main/thrift/users.thrift index 35ec425054..80444b0e11 100644 --- a/libraries/datahandler/src/main/thrift/users.thrift +++ b/libraries/datahandler/src/main/thrift/users.thrift @@ -12,6 +12,7 @@ include "sw360.thrift" namespace java org.eclipse.sw360.datahandler.thrift.users namespace php sw360.thrift.users +typedef sw360.AddDocumentRequestSummary AddDocumentRequestSummary typedef sw360.RequestStatus RequestStatus typedef sw360.PaginationData PaginationData @@ -68,6 +69,7 @@ struct User { 23: optional list primaryRoles, 24: optional bool deactivated 25: optional map oidcClientInfos, + 26: optional string password } struct ClientMetadata { @@ -123,7 +125,7 @@ service UserService { /** * add SW360-user to database, user.email is used as id **/ - RequestStatus addUser(1: User user); + AddDocumentRequestSummary addUser(1: User user); /** * update SW360-user in database diff --git a/rest/authorization-server/src/main/java/org/eclipse/sw360/rest/authserver/security/Sw360GrantedAuthoritiesCalculator.java b/rest/authorization-server/src/main/java/org/eclipse/sw360/rest/authserver/security/Sw360GrantedAuthoritiesCalculator.java index 98fe90ed64..918e809c2f 100644 --- a/rest/authorization-server/src/main/java/org/eclipse/sw360/rest/authserver/security/Sw360GrantedAuthoritiesCalculator.java +++ b/rest/authorization-server/src/main/java/org/eclipse/sw360/rest/authserver/security/Sw360GrantedAuthoritiesCalculator.java @@ -38,7 +38,7 @@ public List generateFromUser(User user) { List grantedAuthorities = new ArrayList<>(); grantedAuthorities.add(new SimpleGrantedAuthority(READ.getAuthority())); - if(user != null) { + if (user != null) { if (PermissionUtils.isUserAtLeast(Sw360AuthorizationServer.CONFIG_WRITE_ACCESS_USERGROUP, user)) { grantedAuthorities.add(new SimpleGrantedAuthority(Sw360GrantedAuthority.WRITE.getAuthority())); } diff --git a/rest/resource-server/src/docs/asciidoc/api-guide.adoc b/rest/resource-server/src/docs/asciidoc/api-guide.adoc index b6136300db..67a2fc76c6 100644 --- a/rest/resource-server/src/docs/asciidoc/api-guide.adoc +++ b/rest/resource-server/src/docs/asciidoc/api-guide.adoc @@ -250,6 +250,9 @@ Token response elements | `Write Access` | rest.write.access.usergroup | SW360_ADMIN +| `Admin Access` +| rest.admin.access.usergroup +| SW360_ADMIN | `Enable Token Generator` | rest.apitoken.generator.enable | false diff --git a/rest/resource-server/src/docs/asciidoc/users.adoc b/rest/resource-server/src/docs/asciidoc/users.adoc index 1d1cc7334e..bc48542f5a 100644 --- a/rest/resource-server/src/docs/asciidoc/users.adoc +++ b/rest/resource-server/src/docs/asciidoc/users.adoc @@ -53,7 +53,16 @@ include::{snippets}/should_document_get_user/links.adoc[] [[resources-users-create]] ==== Creating a user -Creating a user is currently not supported by the REST API. -A `POST` request will result in an error. +A `POST` request will create a user(not in Liferay). +===== Request structure +include::{snippets}/should_document_create_user/request-fields.adoc[] +===== Response structure +include::{snippets}/should_document_create_user/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_create_user/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_create_user/http-response.adoc[] \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java index abebaf036e..5b9555e348 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java @@ -14,6 +14,7 @@ import java.util.Set; import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.rest.common.PropertyUtils; import org.eclipse.sw360.rest.common.Sw360CORSFilter; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; @@ -55,6 +56,10 @@ public class Sw360ResourceServer extends SpringBootServletInitializer { public static final String JWKS_ENDPOINT_URL; public static final Boolean IS_JWKS_VALIDATION_ENABLED; public static final Boolean IS_FORCE_UPDATE_ENABLED; + public static final UserGroup CONFIG_WRITE_ACCESS_USERGROUP; + public static final UserGroup CONFIG_ADMIN_ACCESS_USERGROUP; + private static final String DEFAULT_WRITE_ACCESS_USERGROUP = UserGroup.SW360_ADMIN.name(); + private static final String DEFAULT_ADMIN_ACCESS_USERGROUP = UserGroup.SW360_ADMIN.name(); static { Properties props = CommonUtils.loadProperties(Sw360ResourceServer.class, SW360_PROPERTIES_FILE_PATH); @@ -69,6 +74,8 @@ public class Sw360ResourceServer extends SpringBootServletInitializer { IS_JWKS_VALIDATION_ENABLED = Boolean.parseBoolean(props.getProperty("jwks.validation.enabled", "false")); IS_FORCE_UPDATE_ENABLED = Boolean.parseBoolean( System.getProperty("RunRestForceUpdateTest", props.getProperty("rest.force.update.enabled", "false"))); + CONFIG_WRITE_ACCESS_USERGROUP = UserGroup.valueOf(props.getProperty("rest.write.access.usergroup", DEFAULT_WRITE_ACCESS_USERGROUP)); + CONFIG_ADMIN_ACCESS_USERGROUP = UserGroup.valueOf(props.getProperty("rest.admin.access.usergroup", DEFAULT_ADMIN_ACCESS_USERGROUP)); } @Bean diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index dc77e848e5..df3be9fcf0 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -254,7 +254,7 @@ static abstract class EmbeddedProjectMixin extends ProjectMixin { @JsonIgnoreProperties({ "id", "revision", - "externalid", + "setPassword", "wantsMailNotification", "setWantsMailNotification", "setId", @@ -301,6 +301,10 @@ static abstract class UserMixin extends User { @Override @JsonProperty("lastName") abstract public String getLastname(); + + @Override + @JsonProperty(access = Access.WRITE_ONLY) + abstract public String getPassword(); } @JsonInclude(JsonInclude.Include.NON_NULL) 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 215e7d6e0a..767e48a4c3 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 @@ -121,7 +121,14 @@ public class RestControllerHelper { public User getSw360UserFromAuthentication() { try { - String userId = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String userId; + Object principle = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (principle instanceof String) { + userId = principle.toString(); + } else { + org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) principle; + userId = user.getUsername(); + } return userService.getUserByEmailOrExternalId(userId); } catch (RuntimeException e) { throw new AuthenticationServiceException("Could not load user from authentication."); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/filter/EndpointsFilter.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/filter/EndpointsFilter.java new file mode 100644 index 0000000000..cc39598e5b --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/filter/EndpointsFilter.java @@ -0,0 +1,43 @@ +/* +SPDX-FileCopyrightText: © 2022 Siemens AG +SPDX-License-Identifier: EPL-2.0 +*/ +package org.eclipse.sw360.rest.resourceserver.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class EndpointsFilter extends OncePerRequestFilter { + + private static final String ERROR_MESSAGE = "Service is disabled"; + + private static final String CREATE_USER_ENDPOINT = "/resource/api/users"; + + @Value("${sw360.rest.api.createuser.disabled}") + boolean disabledUsrCreation; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String requestURI = request.getRequestURI(); + String method = request.getMethod(); + + if (disabledUsrCreation && requestURI.equalsIgnoreCase(CREATE_USER_ENDPOINT) && method.equals("POST")) { + response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), ERROR_MESSAGE); + } else { + filterChain.doFilter(request, response); + } + + } + +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java index bbdd7705dd..5ea9ac09b8 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java @@ -10,11 +10,15 @@ package org.eclipse.sw360.rest.resourceserver.security; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.eclipse.sw360.rest.resourceserver.core.SimpleAuthenticationEntryPoint; import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter; import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationProvider; +import org.eclipse.sw360.rest.resourceserver.security.basic.Sw360CustomUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpMethod; @@ -24,10 +28,12 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @Profile("!SECURITY_MOCK") @@ -37,12 +43,17 @@ @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements ResourceServerConfigurer { + private final Logger log = LogManager.getLogger(this.getClass()); + @Autowired private ApiTokenAuthenticationFilter filter; @Autowired private ApiTokenAuthenticationProvider authProvider; + @Autowired + private Sw360CustomUserDetailsService userDetailsService; + @Autowired private ResourceServerProperties resourceServerProperties; @@ -53,6 +64,11 @@ public ResourceServerConfiguration(ResourceServerProperties resourceServerProper @Override protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) { authenticationManagerBuilder.authenticationProvider(this.authProvider); + try { + authenticationManagerBuilder.userDetailsService(userDetailsService); + } catch (Exception e) { + log.error("Error in Authentication", e); + } } @Override @@ -73,6 +89,7 @@ public void configure(HttpSecurity http) throws Exception { http .addFilterBefore(filter, BasicAuthenticationFilter.class) .authenticationProvider(authProvider) + .userDetailsService(userDetailsService) .httpBasic() .and() .authorizeRequests() @@ -86,4 +103,9 @@ public void configure(HttpSecurity http) throws Exception { .antMatchers(HttpMethod.PATCH, "/api/**").hasAuthority("WRITE").and() .csrf().disable().exceptionHandling().authenticationEntryPoint(saep); } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360CustomUserDetailsService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360CustomUserDetailsService.java new file mode 100644 index 0000000000..bb382fc4c9 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360CustomUserDetailsService.java @@ -0,0 +1,35 @@ +/* +SPDX-FileCopyrightText: © 2022 Siemens AG +SPDX-License-Identifier: EPL-2.0 +*/ +package org.eclipse.sw360.rest.resourceserver.security.basic; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.sw360.datahandler.thrift.users.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Profile("!SECURITY_MOCK") +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class Sw360CustomUserDetailsService implements UserDetailsService { + + private static final Logger log = LogManager.getLogger(Sw360CustomUserDetailsService.class); + + @Autowired + Sw360UserDetailsProvider sw360UserDetailsProvider; + + @Override + public UserDetails loadUserByUsername(String userid) { + log.info("Authenticating for the user with username {}", userid); + User user = sw360UserDetailsProvider.provideUserDetails(userid, null); + return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), + Sw360GrantedAuthoritiesCalculator.generateFromUser(user)); + } +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthoritiesCalculator.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthoritiesCalculator.java new file mode 100644 index 0000000000..4ca8f66ce8 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthoritiesCalculator.java @@ -0,0 +1,37 @@ +/* +SPDX-FileCopyrightText: © 2022 Siemens AG +SPDX-License-Identifier: EPL-2.0 +*/ +package org.eclipse.sw360.rest.resourceserver.security.basic; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.sw360.datahandler.permissions.PermissionUtils; +import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * This class offer helper methods to calculate the {@GrantedAuthority} for a + * user. + */ +public class Sw360GrantedAuthoritiesCalculator { + + public static List generateFromUser(User user) { + List grantedAuthorities = new ArrayList<>(); + + grantedAuthorities.add(new SimpleGrantedAuthority(Sw360GrantedAuthority.READ.getAuthority())); + if (user != null) { + if (PermissionUtils.isUserAtLeast(Sw360ResourceServer.CONFIG_WRITE_ACCESS_USERGROUP, user)) { + grantedAuthorities.add(new SimpleGrantedAuthority(Sw360GrantedAuthority.WRITE.getAuthority())); + } + if (PermissionUtils.isUserAtLeast(Sw360ResourceServer.CONFIG_ADMIN_ACCESS_USERGROUP, user)) { + grantedAuthorities.add(new SimpleGrantedAuthority(Sw360GrantedAuthority.ADMIN.getAuthority())); + } + } + + return grantedAuthorities; + } +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthority.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthority.java new file mode 100644 index 0000000000..0208556be2 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360GrantedAuthority.java @@ -0,0 +1,17 @@ +/* +SPDX-FileCopyrightText: © 2022 Siemens AG +SPDX-License-Identifier: EPL-2.0 +*/ +package org.eclipse.sw360.rest.resourceserver.security.basic; + +import org.springframework.security.core.GrantedAuthority; + +public enum Sw360GrantedAuthority implements GrantedAuthority { + + BASIC, READ, WRITE, ADMIN; + + @Override + public String getAuthority() { + return toString(); + } +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360UserDetailsProvider.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360UserDetailsProvider.java new file mode 100644 index 0000000000..247184ae60 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/basic/Sw360UserDetailsProvider.java @@ -0,0 +1,51 @@ +/* +SPDX-FileCopyrightText: © 2022 Siemens AG +SPDX-License-Identifier: EPL-2.0 +*/ +package org.eclipse.sw360.rest.resourceserver.security.basic; + +import org.eclipse.sw360.datahandler.thrift.ThriftClients; +import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.users.UserService; + +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.thrift.TException; +import org.springframework.stereotype.Repository; + +@Repository +public class Sw360UserDetailsProvider { + + private final Logger log = LogManager.getLogger(this.getClass()); + + private ThriftClients thriftClients = new ThriftClients(); + + public User provideUserDetails(String email, String extId) { + User result = null; + + log.debug("Looking up user with email <" + email + "> and external id <" + extId + ">."); + + User user = getUserByEmailOrExternalId(email, extId); + if (user != null) { + log.debug("Found user with email <" + email + "> and external id <" + extId + "> in UserService."); + result = user; + } else { + log.warn("No user found with email <" + email + "> and external id <" + extId + "> in UserService."); + } + + return result; + } + + private User getUserByEmailOrExternalId(String email, String externalId) { + UserService.Iface client = thriftClients.makeUserClient(); + try { + if (StringUtils.isNotEmpty(email) || StringUtils.isNotEmpty(externalId)) { + return client.getByEmailOrExternalId(email, externalId); + } + } catch (TException e) { + // do nothing + } + return null; + } +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java index dbc5cdd876..a7c38e6979 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java @@ -15,9 +15,15 @@ import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.users.UserService; import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.stereotype.Service; import java.util.List; @@ -81,6 +87,28 @@ public User getUserFromClientId(String clientId) { } } + public User addUser(User user) throws TException{ + try { + UserService.Iface sw360UserClient = getThriftUserClient(); + user.setUserGroup(UserGroup.USER); + AddDocumentRequestSummary documentRequestSummary = sw360UserClient.addUser(user); + if (documentRequestSummary.getRequestStatus() == AddDocumentRequestStatus.SUCCESS) { + user.setId(documentRequestSummary.getId()); + return user; + } else if (documentRequestSummary.getRequestStatus() == AddDocumentRequestStatus.DUPLICATE) { + throw new DataIntegrityViolationException("sw360 user with name '" + user.getEmail() + + "' already exists, having database identifier " + documentRequestSummary.getId()); + } else if (documentRequestSummary.getRequestStatus() == AddDocumentRequestStatus.INVALID_INPUT) { + throw new HttpMessageNotReadableException(documentRequestSummary.getMessage()); + } + } catch (SW360Exception sw360Exp) { + throw new HttpMessageNotReadableException(sw360Exp.getMessage()); + } catch (TException e) { + throw new HttpMessageNotReadableException(e.getMessage()); + } + return null; + } + private UserService.Iface getThriftUserClient() throws TTransportException { THttpClient thriftClient = new THttpClient(thriftServerUrl + "/users/thrift"); TProtocol protocol = new TCompactProtocol(thriftClient); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java index 7675e550b8..db915ac1bb 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java @@ -12,6 +12,9 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + +import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.rest.resourceserver.core.HalResource; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; @@ -24,11 +27,17 @@ import org.springframework.hateoas.CollectionModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; @@ -47,6 +56,9 @@ public class UserController implements RepresentationModelProcessor> getUser( return new ResponseEntity<>(halResource, HttpStatus.OK); } + @PostMapping(value = USERS_URL) + public ResponseEntity> createUser(@RequestBody User user) throws TException { + if(CommonUtils.isNullEmptyOrWhitespace(user.getPassword())) { + throw new HttpMessageNotReadableException("Password can not be null or empty or whitespace!"); + } + String encodedPassword = passwordEncoder.encode(user.getPassword()); + user.setPassword(encodedPassword); + User createdUser = userService.addUser(user); + HalResource halResource = createHalUser(createdUser); + URI location = ServletUriComponentsBuilder + .fromCurrentRequest().path("/{id}") + .buildAndExpand(createdUser.getId()).toUri(); + + return ResponseEntity.created(location).body(halResource); + } + @Override public RepositoryLinksResource process(RepositoryLinksResource resource) { resource.add(linkTo(UserController.class).slash("api/users").withRel("users")); diff --git a/rest/resource-server/src/main/resources/application.yml b/rest/resource-server/src/main/resources/application.yml index eeb51c3aa5..c250921975 100644 --- a/rest/resource-server/src/main/resources/application.yml +++ b/rest/resource-server/src/main/resources/application.yml @@ -59,3 +59,7 @@ sw360: couchdb-url: ${SW360_COUCHDB_URL:http://localhost:5984} cors: allowed-origin: ${SW360_CORS_ALLOWED_ORIGIN:#{null}} + rest: + api: + createuser: + disabled: true \ No newline at end of file diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java index 1d6c1a9524..31095d9cbf 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java @@ -10,6 +10,8 @@ package org.eclipse.sw360.rest.resourceserver.restdocs; import com.google.common.collect.Sets; + +import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.rest.resourceserver.TestHelper; @@ -30,13 +32,14 @@ import java.util.Map; import java.util.Set; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @@ -54,7 +57,7 @@ public class UserSpecTest extends TestRestDocsSpecBase { private User user; @Before - public void before() { + public void before() throws TException { List userList = new ArrayList<>(); Map> secondaryDepartmentsAndRoles = new HashMap>(); @@ -81,6 +84,10 @@ public void before() { given(this.userServiceMock.getUserByEmail("admin@sw360.org")).willReturn(user); given(this.userServiceMock.getUser("4784587578e87989")).willReturn(user); + given(this.userServiceMock.getUser("4784587578e87989")).willReturn(user); + when(this.userServiceMock.addUser(any())).then( + invocation -> new User("test@sw360.org", "DEPARTMENT").setId("1234567890").setFullname("FTest lTest") + .setGivenname("FTest").setLastname("lTest").setUserGroup(UserGroup.USER)); User user2 = new User(); user2.setEmail("jane@sw360.org"); @@ -115,6 +122,42 @@ public void should_document_get_users() throws Exception { ))); } + @Test + public void should_document_create_user() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + Map user = new HashMap<>(); + user.put("fullName", "FTest lTest"); + user.put("givenName", "FTest"); + user.put("lastName", "lTest"); + user.put("email", "test@sw360.org"); + user.put("department", "DEPARTMENT"); + user.put("password", "12345"); + mockMvc.perform(post("/api/users") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(user)) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isCreated()) + .andDo(this.documentationHandler.document( + requestFields( + fieldWithPath("email").description("The email of the user"), + fieldWithPath("fullName").description("The full name of the user"), + fieldWithPath("givenName").description("The given name of the user"), + fieldWithPath("lastName").description("The last name of the user"), + fieldWithPath("department").description("The department of the user"), + fieldWithPath("password").description("The password of the user") + ), + responseFields( + subsectionWithPath("email").description("The email of the user"), + subsectionWithPath("userGroup").description("The user group of the user"), + subsectionWithPath("department").description("The department of the user"), + subsectionWithPath("fullName").description("The full name of the user"), + subsectionWithPath("givenName").description("The given name of the user"), + subsectionWithPath("lastName").description("The last name of the user"), + subsectionWithPath("deactivated").description("Is user deactivated"), + subsectionWithPath("_links").description("<> to user resource") + ))); + } + @Test public void should_document_get_user_by_id() throws Exception { String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); From 61c0a1b7c616135f3e2090ecf7312cd5341cb10a Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Wed, 19 Apr 2023 20:38:22 +0200 Subject: [PATCH 10/20] fix(docker): Remove couchdb-clucene from the slim jars Current code for couchdb clucene was introducing old duplicated deps in the shared pool, causing conflicts on tomcat bootstrap loading. Issue: https://github.com/eclipse-sw360/sw360/issues/1808 Signed-off-by: Helio Chissini de Castro --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9eaa2c878d..029e3e48e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -199,12 +199,13 @@ RUN --mount=type=bind,target=/build/sw360,rw \ WORKDIR /sw360_tomcat_webapps/ COPY scripts/create-slim-war-files.sh /bin/slim.sh -COPY --from=sw360clucene /couchdb-lucene.war /sw360_tomcat_webapps + RUN bash /bin/slim.sh FROM scratch AS sw360 COPY --from=sw360build /sw360_deploy /sw360_deploy COPY --from=sw360build /sw360_tomcat_webapps /sw360_tomcat_webapps +COPY --from=sw360clucene /couchdb-lucene.war /sw360_tomcat_webapps COPY --from=sw360build /etc/sw360 /etc/sw360 #-------------------------------------------------------------------------------------------------- From 9d566af03ad7ec02f26fc998d39371d0e87c2223 Mon Sep 17 00:00:00 2001 From: Keerthi B L Date: Fri, 3 Mar 2023 11:08:14 +0530 Subject: [PATCH 11/20] feat(rest):New end point for my components Signed-off-by: Keerthi B L --- .../src/docs/asciidoc/components.adoc | 17 +++++++++++++ .../component/ComponentController.java | 24 +++++++++++++++++++ .../component/Sw360ComponentService.java | 5 ++++ .../restdocs/ComponentSpecTest.java | 22 +++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/rest/resource-server/src/docs/asciidoc/components.adoc b/rest/resource-server/src/docs/asciidoc/components.adoc index 62a841d640..807a18adf4 100644 --- a/rest/resource-server/src/docs/asciidoc/components.adoc +++ b/rest/resource-server/src/docs/asciidoc/components.adoc @@ -329,3 +329,20 @@ include::{snippets}/should_document_get_mysubscriptions_components/http-response ===== Links include::{snippets}/should_document_get_mysubscriptions_components/links.adoc[] + +[[resources-mycomponents-components]] +==== Listing my components + +A `GET` request will list all of the service's getMyComponents components. + +===== Response structure +include::{snippets}/should_document_get_mycomponents_components/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_mycomponents_components/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_mycomponents_components/http-response.adoc[] + +===== Links +include::{snippets}/should_document_get_mycomponents_components/links.adoc[] \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java index 152c562f06..71f8d18188 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java @@ -426,4 +426,28 @@ private void addEmbeddedDefaultVendor(HalResource halComponent, Vendo halDefaultVendor.add(vendorSelfLink); halComponent.addEmbeddedResource("defaultVendor", halDefaultVendor); } + + @RequestMapping(value = COMPONENTS_URL + "/getMyComponents", method = RequestMethod.GET) + public ResponseEntity> getMyComponents(Pageable pageable, HttpServletRequest request) + throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + List sw360Components = componentService.getMyComponentsForUser(sw360User); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, + sw360Components, SW360Constants.TYPE_COMPONENT); + List> componentResources = new ArrayList<>(); + + paginationResult.getResources().stream().forEach(c -> { + Component embeddedComponent = restControllerHelper.convertToEmbeddedComponent(c, null); + EntityModel embeddedComponentResource = EntityModel.of(embeddedComponent); + if (embeddedComponentResource == null) { + return; + } + componentResources.add(embeddedComponentResource); + }); + + CollectionModel finalResources = restControllerHelper.generatePagesResource(paginationResult, + componentResources); + HttpStatus status = finalResources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(finalResources, status); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java index b8c0c50ef2..df9a2f2357 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java @@ -179,4 +179,9 @@ private ProjectService.Iface getThriftProjectClient() throws TTransportException TProtocol protocol = new TCompactProtocol(thriftClient); return new ProjectService.Client(protocol); } + + public List getMyComponentsForUser(User sw360User) throws TException { + ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); + return sw360ComponentClient.getMyComponents(sw360User); + } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java index dcc5f45f8a..9ca0fbe673 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java @@ -191,6 +191,7 @@ public void before() throws TException, IOException { given(this.componentServiceMock.getComponentsForUser(any())).willReturn(componentList); given(this.componentServiceMock.getRecentComponents(any())).willReturn(componentList); given(this.componentServiceMock.getComponentSubscriptions(any())).willReturn(componentList); + given(this.componentServiceMock.getMyComponentsForUser(any())).willReturn(componentList); given(this.componentServiceMock.getComponentForUserById(eq("17653524"), any())).willReturn(angularComponent); given(this.componentServiceMock.getComponentForUserById(eq("98745"), any())).willReturn(testComponent); given(this.componentServiceMock.getProjectsByComponentId(eq("17653524"), any())).willReturn(projectList); @@ -692,4 +693,25 @@ private RestDocumentationResultHandler documentComponentProperties() { fieldWithPath("setBusinessUnit").description("Whether or not a business unit is set for the component") )); } + + @Test + public void should_document_get_mycomponents_components() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + + mockMvc.perform(get("/api/components/getMyComponents") + .header("Authorization", "Bearer " + accessToken).accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + links(linkWithRel("curies").description("Curies are used for online documentation")), + responseFields( + subsectionWithPath("_embedded.sw360:components.[]name") + .description("The name of the component"), + subsectionWithPath("_embedded.sw360:components.[]componentType") + .description("The component type, possible values are: " + + Arrays.asList(ComponentType.values())), + subsectionWithPath("_embedded.sw360:components") + .description("An array of <>"), + subsectionWithPath("_links") + .description("<> to other resources")))); + } } From b42ea4fd32bb11c8bc7e41b62f64cda4324e82eb Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Wed, 19 Apr 2023 20:02:41 +0200 Subject: [PATCH 12/20] fix(versions): Normalize javax.activation version Issue: https://github.com/eclipse-sw360/sw360/issues/1808 Signed-off-by: Helio Chissini de Castro --- pom.xml | 22 ++++++++++++++++------ rest/resource-server/pom.xml | 11 +++++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 9088215a31..bacb7c0698 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,9 @@ ~ ~ SPDX-License-Identifier: EPL-2.0 --> - + 4.0.0 org.eclipse.sw360 @@ -98,6 +100,7 @@ 2.13.4.2 2.13.4 1.3.2 + 1.1.1 0.1.54 0.17.0 3.3.2 @@ -342,6 +345,11 @@ httpcore 4.4.8 + + javax.activation + activation + 1.1.1 + com.google.code.gson gson @@ -718,8 +726,8 @@ origin/main - - + + *.java @@ -779,9 +787,11 @@ ${artifact.deploy.dir} @@ -886,4 +896,4 @@ - + \ No newline at end of file diff --git a/rest/resource-server/pom.xml b/rest/resource-server/pom.xml index f6e3ad4bb4..9e37af6cea 100644 --- a/rest/resource-server/pom.xml +++ b/rest/resource-server/pom.xml @@ -10,7 +10,9 @@ ~ SPDX-License-Identifier: EPL-2.0 --> - + 4.0.0 @@ -187,7 +189,7 @@ javax.activation activation - 1.1.1 + ${javax-activation.version} org.springframework.security.oauth.boot @@ -301,7 +303,8 @@ - ../../frontend/sw360-portlet/target/mkdocs/generate-mkdocs + + ../../frontend/sw360-portlet/target/mkdocs/generate-mkdocs @@ -323,4 +326,4 @@ - + \ No newline at end of file From 43a07df64f2264e16de6ce30e8d29fd54d5854b3 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Sat, 22 Apr 2023 19:11:00 +0200 Subject: [PATCH 13/20] fix(ci): Cache now use the right naming Signed-off-by: Helio Chissini de Castro --- .github/workflows/build_and_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d375cc1432..099a4dc683 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -71,7 +71,7 @@ jobs: /usr/share/thrift/${{ env.THRIFT_VERSION }} key: ${{ runner.os }}-thrift-${{ hashFiles('/usr/local/bin/thrift') }} restore-keys: | - ${{ runner.os }}-thrift- + ${{ runner.os }}-thrift-${{ hashFiles('/usr/local/bin/thrift') }} - name: Install Thrift if: steps.cache-thrift.outputs.cache-hit != 'true' @@ -85,7 +85,7 @@ jobs: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | - ${{ runner.os }}-maven- + ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - name: Build SW360 run: | From 96387f9c7566df48a079d3dbb93af58b34e643dc Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Sat, 22 Apr 2023 19:24:13 +0200 Subject: [PATCH 14/20] fix(cache): Key was invalid due file not exists and hash attempt fail Signed-off-by: Helio Chissini de Castro --- .github/workflows/build_and_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 099a4dc683..60c80c647a 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -69,9 +69,9 @@ jobs: path: | /usr/local/bin/thrift /usr/share/thrift/${{ env.THRIFT_VERSION }} - key: ${{ runner.os }}-thrift-${{ hashFiles('/usr/local/bin/thrift') }} + key: ${{ runner.os }}-thrift-${{ env.THRIFT_VERSION }} restore-keys: | - ${{ runner.os }}-thrift-${{ hashFiles('/usr/local/bin/thrift') }} + ${{ runner.os }}-thrift-${{ env.THRIFT_VERSION }} - name: Install Thrift if: steps.cache-thrift.outputs.cache-hit != 'true' From d4e4ac7646e37e3ac9345638f3b250fb666fdb47 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Sat, 22 Apr 2023 17:38:25 +0200 Subject: [PATCH 15/20] fix(deps): Update to current httpcore5 release Old 4 series is deprecated and not maintained anymore. Signed-off-by: Helio Chissini de Castro --- libraries/datahandler/pom.xml | 46 +++++++++++++++++------------------ pom.xml | 7 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/libraries/datahandler/pom.xml b/libraries/datahandler/pom.xml index 89ffb74434..b57c1955c5 100644 --- a/libraries/datahandler/pom.xml +++ b/libraries/datahandler/pom.xml @@ -9,8 +9,8 @@ ~ SPDX-License-Identifier: EPL-2.0 --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -28,7 +28,7 @@ - org.eclipse.sw360.datahandler-${project.version} + org.eclipse.sw360.datahandler-${project.version} org.apache.thrift.tools @@ -62,17 +62,17 @@ ${build-helper-maven-plugin.version} - thrift-sources - generate-sources - - add-source - - - - target/generated-sources/thrift - - - + thrift-sources + generate-sources + + add-source + + + + target/generated-sources/thrift + + + thrift-test-sources generate-test-sources @@ -108,7 +108,7 @@ - + ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -155,15 +155,15 @@ org.ektorp - com.google.code.gson - gson - ${gson.version} + com.google.code.gson + gson + ${gson.version} - + com.cloudant cloudant-client 2.19.1 - + com.fasterxml.jackson.core @@ -182,8 +182,8 @@ findbugs-annotations - org.apache.httpcomponents - httpcore + org.apache.httpcomponents.core5 + httpcore5 org.apache.thrift @@ -227,4 +227,4 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index bacb7c0698..f52d17eee7 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,7 @@ 31.1-jre 1.3 4.5.14 + 5.2.1 2.3.2 2.13.4 2.13.4.2 @@ -341,9 +342,9 @@ ${jackson.annotations.version} - org.apache.httpcomponents - httpcore - 4.4.8 + org.apache.httpcomponents.core5 + httpcore5 + ${httpcore5.version} javax.activation From c2844e30cefda0eb39ec56f8f240473ef3cb6d02 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Sun, 23 Apr 2023 17:57:46 +0200 Subject: [PATCH 16/20] fix(deps): Revert httpcore and fix httpcore and spring-boot Spring boot 2.x requires httpcore series 4 as dependencies, so usage of httpcore5 would force to upgrade to newer spring boot, too big change to simply normalize a single version. Signed-off-by: Helio Chissini de Castro --- libraries/datahandler/pom.xml | 4 ++-- pom.xml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/datahandler/pom.xml b/libraries/datahandler/pom.xml index b57c1955c5..5bf1da78e2 100644 --- a/libraries/datahandler/pom.xml +++ b/libraries/datahandler/pom.xml @@ -182,8 +182,8 @@ findbugs-annotations - org.apache.httpcomponents.core5 - httpcore5 + org.apache.httpcomponents + httpcore org.apache.thrift diff --git a/pom.xml b/pom.xml index f52d17eee7..0314810787 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ 31.1-jre 1.3 4.5.14 - 5.2.1 + 4.4.16 2.3.2 2.13.4 2.13.4.2 @@ -134,7 +134,7 @@ 1.7.30 2.29.0 5.3.19 - 2.6.6 + 2.7.11 2.0.6.RELEASE 2.5.1.RELEASE 1.1.1.RELEASE @@ -342,9 +342,9 @@ ${jackson.annotations.version} - org.apache.httpcomponents.core5 - httpcore5 - ${httpcore5.version} + org.apache.httpcomponents + httpcore + ${httpcore.version} javax.activation From 6479d88944d69b9aaf58e0e7da37f0fc57a15fe0 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Wed, 19 Apr 2023 22:35:03 +0200 Subject: [PATCH 17/20] fix(libs): Normalize json versions On current codebase, sw360 is using 4 json libraries. Google json and Cliftonlabs json have name clash. TODO: Change all code to use one single library Issue: https://github.com/eclipse-sw360/sw360/issues/1808 Signed-off-by: Helio Chissini de Castro --- backend/src/src-licenseinfo/pom.xml | 4 --- backend/src/src-licenses/pom.xml | 12 +++----- backend/src/src-vmcomponents/pom.xml | 11 ++------ pom.xml | 42 ++++++++++++++++++---------- utils/pom.xml | 15 +++++----- 5 files changed, 41 insertions(+), 43 deletions(-) diff --git a/backend/src/src-licenseinfo/pom.xml b/backend/src/src-licenseinfo/pom.xml index 1dbd8a027c..8168fd4c06 100644 --- a/backend/src/src-licenseinfo/pom.xml +++ b/backend/src/src-licenseinfo/pom.xml @@ -31,10 +31,6 @@ org.spdx tools-java - - com.googlecode.json-simple - json-simple - org.apache.logging.log4j log4j-1.2-api diff --git a/backend/src/src-licenses/pom.xml b/backend/src/src-licenses/pom.xml index 3f5e25da52..59a9d13506 100644 --- a/backend/src/src-licenses/pom.xml +++ b/backend/src/src-licenses/pom.xml @@ -9,8 +9,8 @@ ~ SPDX-License-Identifier: EPL-2.0 --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -22,7 +22,7 @@ src-licenses jar src-licenses - + 5.1.2 @@ -32,10 +32,6 @@ org.spdx tools-java - - com.googlecode.json-simple - json-simple - org.apache.logging.log4j @@ -52,4 +48,4 @@ provided - + \ No newline at end of file diff --git a/backend/src/src-vmcomponents/pom.xml b/backend/src/src-vmcomponents/pom.xml index cae6137cef..85441b9e6a 100644 --- a/backend/src/src-vmcomponents/pom.xml +++ b/backend/src/src-vmcomponents/pom.xml @@ -4,8 +4,8 @@ ~ SPDX-License-Identifier: EPL-2.0 --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -30,11 +30,6 @@ src-vulnerabilities ${revision} - - com.googlecode.json-simple - json-simple - 1.1.1 - com.github.tomakehurst wiremock @@ -46,4 +41,4 @@ 4.4.4 - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0314810787..4bba0cd9c1 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,14 @@ 3.3.2 2.12.0 + + 2.10.1 + 20230227 + 2.8.0 + 2.4.10 + 2.3.1 + 2.14.2 + 1.12.13 2.19.1 @@ -91,15 +99,11 @@ 1.0.1 1.3.9-1 2.3.2 - 2.8.9 31.1-jre 1.3 4.5.14 4.4.16 2.3.2 - 2.13.4 - 2.13.4.2 - 2.13.4 1.3.2 1.1.1 0.1.54 @@ -142,7 +146,6 @@ 0.16.0 2.26.0 1.1.5 - 1.1.1 @@ -191,7 +194,22 @@ com.github.cliftonlabs json-simple - 2.3.1 + ${json-simple.version} + + + org.json + json + ${json.version} + + + net.minidev + json-smart + ${json-smart.version} + + + com.jayway.jsonpath + json-path + ${json-path.version} com.github.package-url @@ -329,17 +347,17 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.databind.version} + ${jackson.version} com.fasterxml.jackson.core jackson-core - ${jackson.core.version} + ${jackson.version} com.fasterxml.jackson.core jackson-annotations - ${jackson.annotations.version} + ${jackson.version} org.apache.httpcomponents @@ -366,12 +384,6 @@ tools-java ${org.spdx.tools.java.version} - - com.googlecode.json-simple - json-simple - ${com.googlecode.json-simple.version} - compile - org.apache.velocity velocity-engine-core diff --git a/utils/pom.xml b/utils/pom.xml index 4ba7440072..4aa974ebf8 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -10,7 +10,9 @@ ~ SPDX-License-Identifier: EPL-2.0 --> - + sw360 org.eclipse.sw360 @@ -34,7 +36,8 @@ copy-dependencies - + ${jars.deploy.dir} false false @@ -44,7 +47,7 @@ - + @@ -56,22 +59,18 @@ com.fasterxml.jackson.core jackson-annotations - ${jackson.annotations.version} com.fasterxml.jackson.core jackson-core - ${jackson.core.version} com.fasterxml.jackson.core jackson-databind - ${jackson.databind.version} com.google.code.gson gson - ${gson.version} commons-codec @@ -119,4 +118,4 @@ ${thrift.version} - + \ No newline at end of file From e1dd33f43c680d0025fb0e4cb1acfcb2d4f5c914 Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Mon, 24 Apr 2023 14:39:49 +0530 Subject: [PATCH 18/20] fix(rest): added endpoint for release subscriptions Add endpoint to list subscriptions of releases. Signed-off-by: Gaurav Mishra --- .../src/docs/asciidoc/releases.adoc | 23 ++++++++++++++++++- .../release/ReleaseController.java | 20 ++++++++++++++-- .../release/Sw360ReleaseService.java | 5 ++++ .../restdocs/ReleaseSpecTest.java | 20 ++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/releases.adoc b/rest/resource-server/src/docs/asciidoc/releases.adoc index 77f632eca7..8f36b9fa04 100644 --- a/rest/resource-server/src/docs/asciidoc/releases.adoc +++ b/rest/resource-server/src/docs/asciidoc/releases.adoc @@ -392,4 +392,25 @@ include::{snippets}/should_document_get_release_vulnerabilities/curl-request.ado include::{snippets}/should_document_get_release_vulnerabilities/http-response.adoc[] ===== Links -include::{snippets}/should_document_get_release_vulnerabilities/links.adoc[] \ No newline at end of file +include::{snippets}/should_document_get_release_vulnerabilities/links.adoc[] + +[[resources-release-subscription]] +==== Listing releases subscription + +A `GET` request will list all the service's releases subscription. + +===== Response structure + +include::{snippets}/should_document_get_release_subscription/response-fields.adoc[] + +===== Example request + +include::{snippets}/should_document_get_release_subscription/curl-request.adoc[] + +===== Example response + +include::{snippets}/should_document_get_release_subscription/http-response.adoc[] + +===== Links + +include::{snippets}/should_document_get_release_subscription/links.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java index 062307d57f..02508c1ad5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java @@ -218,9 +218,25 @@ public ResponseEntity>> getVulnerabil vulnerabilityResources.add(EntityModel.of(vulnerability)); }); CollectionModel> resources = CollectionModel.of(vulnerabilityResources); - return new ResponseEntity<>(resources,HttpStatus.OK); + return new ResponseEntity<>(resources, HttpStatus.OK); + } + + @GetMapping(value = RELEASES_URL + "/mySubscriptions") + public ResponseEntity> getReleaseSubscription() throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + List sw360Releases = releaseService.getReleaseSubscriptions(sw360User); + + List resources = new ArrayList<>(); + sw360Releases.forEach(c -> { + Release embeddedComponent = restControllerHelper.convertToEmbeddedRelease(c); + resources.add(EntityModel.of(embeddedComponent)); + }); + + CollectionModel finalResources = restControllerHelper.createResources(resources); + HttpStatus status = finalResources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(finalResources, status); } - + @RequestMapping(value = RELEASES_URL + "/usedBy" + "/{id}", method = RequestMethod.GET) public ResponseEntity> getUsedByResourceDetails(@PathVariable("id") String id) throws TException { diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java index 71ae25028a..70c04e3e51 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java @@ -134,6 +134,11 @@ public Release setComponentDependentFieldsInRelease(Release releaseById, User sw return releaseById; } + public List getReleaseSubscriptions(User sw360User) throws TException { + ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); + return sw360ComponentClient.getSubscribedReleases(sw360User); + } + @Override public Set searchByExternalIds(Map> externalIds, User user) throws TException { ComponentService.Iface sw360ComponentClient = getThriftComponentClient(); diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index 667dc9a5da..5c8f54db7d 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -256,6 +256,7 @@ public void before() throws TException, IOException { given(this.releaseServiceMock.getReleasesForUser(any())).willReturn(releaseList); given(this.releaseServiceMock.getRecentReleases(any())).willReturn(releaseList); + given(this.releaseServiceMock.getReleaseSubscriptions(any())).willReturn(releaseList); given(this.releaseServiceMock.getReleaseForUserById(eq(release.getId()), any())).willReturn(release); given(this.releaseServiceMock.getReleaseForUserById(eq(testRelease.getId()), any())).willReturn(testRelease); given(this.releaseServiceMock.getProjectsByRelease(eq(release.getId()), any())).willReturn(projectList); @@ -824,4 +825,23 @@ public void should_document_get_release_vulnerabilities() throws Exception { subsectionWithPath("_links").description("<> to other resources") ))); } + + @Test + public void should_document_get_release_subscription() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/releases/mySubscriptions") + .header("Authorization", "Bearer " + accessToken) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + links( + linkWithRel("curies").description("Curies are used for online documentation") + ), + responseFields( + subsectionWithPath("_embedded.sw360:releases.[]name").description("The name of the release"), + subsectionWithPath("_embedded.sw360:releases.[]version").description("The version of the release"), + subsectionWithPath("_embedded.sw360:releases").description("An array of <>"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } } From 21833c85c49e235122be2a4d5ea8223dad1e7571 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Mon, 24 Apr 2023 11:18:52 +0200 Subject: [PATCH 19/20] ci(cache): Give GH_ACTIONS permissions to reach cache Signed-off-by: Helio Chissini de Castro --- .github/workflows/build_and_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 60c80c647a..06dd6034b4 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -18,6 +18,8 @@ on: paths-ignore: - "**.md" +permissions: write-all + env: COUCHDB_USER: admin COUCHDB_PASSWORD: password From cd6d5cfed3c5169924537da09e2e5ce00fd9b91c Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Mon, 24 Apr 2023 12:45:41 +0200 Subject: [PATCH 20/20] ci(cache): Give GH_ACTIONS permissions to reach cache Signed-off-by: Helio Chissini de Castro --- .github/workflows/docker_deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker_deploy.yml b/.github/workflows/docker_deploy.yml index f80e4e01a3..c7045cac80 100644 --- a/.github/workflows/docker_deploy.yml +++ b/.github/workflows/docker_deploy.yml @@ -24,6 +24,8 @@ on: env: REGISTRY: ghcr.io +permissions: write-all + jobs: docker_push: if: ${{ github.event.schedule }} == '0 0 * * *' || ${{ github.event.act }}