diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java index b633ba2843..3a2a04c5eb 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java @@ -147,13 +147,21 @@ private Map> getVcsToComponentMap(Li .filter(Objects::nonNull) .filter(ref -> ExternalReference.Type.VCS.equals(ref.getType())) .map(ExternalReference::getUrl) - .map(String::toLowerCase) - .map(url -> StringUtils.removeEnd(url, DOT_GIT)) + .map(url -> sanitizeVcsUrl(url)) .map(url -> new AbstractMap.SimpleEntry<>(url, comp))) .collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); } + private String sanitizeVcsUrl(String url) { + try { + return componentDatabaseHandler.validateVCS(url); + } catch (SW360Exception e) { + log.error(e.getMessage()); + return ""; + } + } + @SuppressWarnings("unchecked") public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user) { RequestSummary requestSummary = new RequestSummary(); @@ -186,8 +194,13 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a // Getting List of org.cyclonedx.model.Component from the Bom List components = CommonUtils.nullToEmptyList(bom.getComponents()); - long vcsCount = components.stream().map(org.cyclonedx.model.Component::getExternalReferences) - .filter(Objects::nonNull).flatMap(List::stream).map(ExternalReference::getType).filter(typeFilter).count(); + /*long vcsCount = components.stream().map(org.cyclonedx.model.Component::getExternalReferences) + .filter(Objects::nonNull).flatMap(List::stream).map(ExternalReference::getType).filter(typeFilter).count();*/ + + //get the count of keys in vcsToComponentMa. This count should not include the key that is empty or null + + long vcsCount = getVcsToComponentMap(components).keySet().stream().filter(str -> str != "").count(); + long componentsCount = components.size(); org.cyclonedx.model.Component compMetadata = bomMetadata.getComponent(); Map> vcsToComponentMap = new HashMap<>(); @@ -198,12 +211,15 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a } else { vcsToComponentMap = getVcsToComponentMap(components); + final Map> validVCSToComponentMap = vcsToComponentMap + .entrySet().stream().filter(entry -> entry.getKey() != "") + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (componentsCount == vcsCount) { - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, validVCSToComponentMap, projectId, attachmentContent); } else if (componentsCount > vcsCount) { - requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + requestSummary = importSbomAsProject(compMetadata, validVCSToComponentMap, projectId, attachmentContent); if (requestSummary.requestStatus.equals(RequestStatus.SUCCESS)) { @@ -236,7 +252,8 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a for (org.cyclonedx.model.Component comp : components) { if (CommonUtils.isNullOrEmptyCollection(comp.getExternalReferences()) - || comp.getExternalReferences().stream().map(ExternalReference::getType).filter(typeFilter).count() == 0) { + || comp.getExternalReferences().stream().map(ExternalReference::getType).filter(typeFilter).count() == 0 + || validVCSToComponentMap.values().stream().flatMap(List::stream).noneMatch(c -> c.equals(comp))) { final var fullName = SW360Utils.getVersionedName(comp.getName(), comp.getVersion()); final var licenses = getLicenseFromBomComponent(comp); @@ -574,8 +591,14 @@ private Map importAllComponentsAsPackages(Map duplicates = componentRepository.getComponentIdsByVCS(component.getVcs().toLowerCase()); - if (duplicates.size() == 1) { - duplicates.forEach(addDocumentRequestSummary::setId); + String vcsUrl = component.getVcs(); + if (isNotNullEmptyOrWhitespace(vcsUrl)) { + try { + vcsUrl = validateVCS(vcsUrl); + component.setVcs(vcsUrl); + } catch (SW360Exception e) { + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT); } - return addDocumentRequestSummary; - }else if(isDuplicate(component, true)) { + if (isDuplicateUsingVcs(vcsUrl)){ + final AddDocumentRequestSummary addDocumentRequestSummary = new AddDocumentRequestSummary() + .setRequestStatus(AddDocumentRequestStatus.DUPLICATE); + Set duplicates = componentRepository.getComponentIdsByVCS(component.getVcs().toLowerCase()); + if (duplicates.size() == 1) { + duplicates.forEach(addDocumentRequestSummary::setId); + } + return addDocumentRequestSummary; + + } + } + + if(isDuplicate(component, true)) { final AddDocumentRequestSummary addDocumentRequestSummary = new AddDocumentRequestSummary() .setRequestStatus(AddDocumentRequestStatus.DUPLICATE); Set duplicates = componentRepository.getComponentIdsByName(component.getName(), true); @@ -590,7 +605,7 @@ public AddDocumentRequestSummary addRelease(Release release, User user) throws S } private boolean isDuplicate(Component component, boolean caseInsenstive){ - return isDuplicate(component.getName(), caseInsenstive); + return isDuplicate(component.getName(), caseInsenstive) && isDuplicateUsingVcs(component.getVcs()); } private boolean isDuplicate(Release release){ @@ -606,9 +621,6 @@ private boolean isDuplicate(String componentName, boolean caseInsenstive) { } private boolean isDuplicateUsingVcs(String vcsUrl){ - if (isNullEmptyOrWhitespace(vcsUrl)) { - return false; - } Set duplicates = componentRepository.getComponentIdsByVCS(vcsUrl.toLowerCase()); return duplicates.size()>0; } @@ -697,6 +709,17 @@ public RequestStatus updateComponent(Component component, User user, boolean for if (categories == null || categories.isEmpty()) { component.setCategories(ImmutableSet.of(DEFAULT_CATEGORY)); } + + String vcsUrl = component.getVcs(); + if (isNotNullEmptyOrWhitespace(vcsUrl)) { + try { + vcsUrl = validateVCS(vcsUrl); + component.setVcs(vcsUrl); + } catch (SW360Exception e) { + return RequestStatus.INVALID_INPUT; + } + } + // Prepare component for database prepareComponent(component); @@ -818,7 +841,7 @@ private void updateEccStatusForRelease(Component component) { } private boolean changeWouldResultInDuplicate(Component before, Component after) { - if (before.getName().equals(after.getName())) { + if (before.getName().equals(after.getName()) && before.getVcs().equals(after.getVcs())) { // sth else was changed, not one of the duplication relevant properties return false; } @@ -3186,4 +3209,30 @@ private AttachmentContent makeAttachmentContent(AttachmentContent content) { throw new RuntimeException(e); } } + + public String validateVCS(String vcs) throws SW360Exception { + // GitHub repository URL Format: https://github.com/supplier/name + if (vcs.toLowerCase().contains("github.com")) { + //split the URL which is the VCS in path segments using java.net.URI + URI uri = URI.create(vcs); + String[] urlParts = uri.getPath().split("/"); + if (urlParts.length >= 3) { + String firstSegment = urlParts[1]; + String secondSegment = urlParts[2].replaceAll("\\.git.*", "").replaceAll("#.*", ""); // Remove .git and anything after, and also remove anything after # + String sanitizedVCS = "https://github.com/" + firstSegment + "/" + secondSegment; + return sanitizedVCS; + } else { + log.error("Invalid GitHub repository URL: " + vcs); + throw new SW360Exception("Invalid GitHub repository URL: " + vcs).setErrorCode(HttpStatus.SC_BAD_REQUEST); + } + } else { + //repository URL formats for other domains yet to be defined + if (!CommonUtils.isValidUrl(vcs)) { + log.error("Invalid VCS URL: " + vcs); + throw new SW360Exception("Invalid VCS URL: " + vcs).setErrorCode(HttpStatus.SC_BAD_REQUEST); + } + return vcs; + } + } + } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java index 382c3283cb..121f70f4ac 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java @@ -25,6 +25,7 @@ public class ErrorMessages { public static final String COMPONENT_NOT_ADDED = "Component could not be added."; public static final String COMPONENT_DUPLICATE = "A component with the same name or VCS already exists."; public static final String COMPONENT_NAMING_ERROR = "Name and Categories of component cannot contain only space characters."; + public static final String INVALID_VCS_OR_LINKED_DOCUMENT = "Invalid VCS or linked document"; public static final String RELEASE_NOT_ADDED = "Release could not be added."; public static final String RELEASE_DUPLICATE = "A release with the same name and version already exists."; public static final String RELEASE_NAME_VERSION_ERROR = "Name and version of release cannot contain only space characters."; @@ -71,7 +72,7 @@ public class ErrorMessages { public static final String ERROR_VENDOR = "Error: Invalid vendor Name or Url."; public static final String ERROR_BULK_DELETING = "Error while bulk deleting"; public static final String ERROR_BULK_DELETING_IN_BACKEND = "Error while bulk deleting in backend."; - + public static final String PACKAGE_NOT_ADDED = "Package could not be added."; public static final String PACKAGE_DUPLICATE = "A package with the same name and version already exists."; public static final String PACKAGE_NAME_VERSION_ERROR = "Name and version of the package cannot contain only space characters."; @@ -93,6 +94,7 @@ public class ErrorMessages { .add(COMPONENT_NOT_ADDED) .add(COMPONENT_DUPLICATE) .add(COMPONENT_NAMING_ERROR) + .add(INVALID_VCS_OR_LINKED_DOCUMENT) .add(RELEASE_NOT_ADDED) .add(RELEASE_DUPLICATE) .add(RELEASE_NAME_VERSION_ERROR) diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java index d9f10a2c80..32ed8a2314 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java @@ -2318,11 +2318,13 @@ public void updateComponent(ActionRequest request, ActionResponse response) thro RequestStatus requestStatus = client.updateComponent(component, user); setSessionMessage(request, requestStatus, "Component", "update", component.getName()); if (RequestStatus.DUPLICATE.equals(requestStatus) || RequestStatus.DUPLICATE_ATTACHMENT.equals(requestStatus) || - RequestStatus.NAMINGERROR.equals(requestStatus)) { + RequestStatus.NAMINGERROR.equals(requestStatus) || RequestStatus.INVALID_INPUT.equals(requestStatus) ) { if(RequestStatus.DUPLICATE.equals(requestStatus)) setSW360SessionError(request, ErrorMessages.COMPONENT_DUPLICATE); else if (RequestStatus.NAMINGERROR.equals(requestStatus)) setSW360SessionError(request, ErrorMessages.COMPONENT_NAMING_ERROR); + else if (RequestStatus.INVALID_INPUT.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.INVALID_VCS_OR_LINKED_DOCUMENT); else setSW360SessionError(request, ErrorMessages.DUPLICATE_ATTACHMENT); response.setRenderParameter(PAGENAME, PAGENAME_EDIT); @@ -2360,6 +2362,11 @@ else if (RequestStatus.NAMINGERROR.equals(requestStatus)) response.setRenderParameter(PAGENAME, PAGENAME_EDIT); prepareRequestForEditAfterDuplicateOrNamingError(request, component); break; + case INVALID_INPUT: + setSW360SessionError(request, ErrorMessages.INVALID_VCS_OR_LINKED_DOCUMENT); + response.setRenderParameter(PAGENAME, PAGENAME_EDIT); + prepareRequestForEditAfterDuplicateOrNamingError(request, component); + break; default: setSW360SessionError(request, ErrorMessages.COMPONENT_NOT_ADDED); response.setRenderParameter(PAGENAME, PAGENAME_VIEW); @@ -2417,6 +2424,8 @@ public void updateRelease(ActionRequest request, ActionResponse response) throws setSW360SessionError(request, ErrorMessages.RELEASE_DUPLICATE); else if (RequestStatus.NAMINGERROR.equals(requestStatus)) setSW360SessionError(request, ErrorMessages.RELEASE_NAME_VERSION_ERROR); + else if (RequestStatus.INVALID_INPUT.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.INVALID_VCS_OR_LINKED_DOCUMENT); else setSW360SessionError(request, ErrorMessages.DUPLICATE_ATTACHMENT); response.setRenderParameter(PAGENAME, PAGENAME_EDIT_RELEASE); diff --git a/frontend/sw360-portlet/src/main/resources/content/Language.properties b/frontend/sw360-portlet/src/main/resources/content/Language.properties index 16f4fae139..8a9dbe3dce 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language.properties @@ -858,7 +858,7 @@ link.to.project=Link to Project link.to.projects=Link to Projects list.of.components.without.version.information=List of Components without version information list.of.invalid.packages.without.purl.or.name.or.version=List of invalid Packages without purl or name or version -list.of.packages.without.vcs.information=List of Packages without VCS information +list.of.packages.without.vcs.information=List of Packages with missing or invalid VCS information list.view=List View loading=Loading... loading.data.for.step.1.please.wait=Loading data for step 1, please wait...