com.metaeffekt.mirror.contents.advisory.AdvisoryEntry Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.mirror.contents.advisory;
import com.metaeffekt.artifact.analysis.utils.CustomCollectors;
import com.metaeffekt.artifact.analysis.utils.FileUtils;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.mirror.contents.base.DescriptionParagraph;
import com.metaeffekt.mirror.contents.base.MatchableDetailsAmbDataClass;
import com.metaeffekt.mirror.contents.base.Reference;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeIdentifier;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeStore;
import com.metaeffekt.mirror.contents.store.ContentIdentifierStore;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeIdentifier;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import lombok.Getter;
import lombok.Setter;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.security.cvss.processor.CvssVectorSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public abstract class AdvisoryEntry extends MatchableDetailsAmbDataClass {
private final static Logger LOG = LoggerFactory.getLogger(AdvisoryEntry.class);
protected final static Set CONVERSION_KEYS_AMB = new HashSet(MatchableDetailsAmbDataClass.CONVERSION_KEYS_AMB) {{
addAll(Arrays.asList(
AdvisoryMetaData.Attribute.NAME.getKey(),
AdvisoryMetaData.Attribute.SOURCE.getKey(),
AdvisoryMetaData.Attribute.SOURCE_IMPLEMENTATION.getKey(),
AdvisoryMetaData.Attribute.URL.getKey(),
AdvisoryMetaData.Attribute.TYPE.getKey(),
AdvisoryMetaData.Attribute.SUMMARY.getKey(),
AdvisoryMetaData.Attribute.DESCRIPTION.getKey(),
AdvisoryMetaData.Attribute.THREAT.getKey(),
AdvisoryMetaData.Attribute.RECOMMENDATIONS.getKey(),
AdvisoryMetaData.Attribute.WORKAROUNDS.getKey(),
AdvisoryMetaData.Attribute.ACKNOWLEDGEMENTS.getKey(),
AdvisoryMetaData.Attribute.REFERENCES.getKey(),
AdvisoryMetaData.Attribute.KEYWORDS.getKey(),
AdvisoryMetaData.Attribute.CREATE_DATE.getKey(),
AdvisoryMetaData.Attribute.CREATE_DATE_FORMATTED.getKey(),
AdvisoryMetaData.Attribute.UPDATE_DATE.getKey(),
AdvisoryMetaData.Attribute.UPDATE_DATE_FORMATTED.getKey(),
AdvisoryMetaData.Attribute.MATCHING_SOURCE.getKey(),
AdvisoryMetaData.Attribute.DATA_SOURCE.getKey(),
AdvisoryMetaData.Attribute.CVSS_VECTORS.getKey()
));
}};
protected final static Set CONVERSION_KEYS_MAP = new HashSet(MatchableDetailsAmbDataClass.CONVERSION_KEYS_MAP) {{
addAll(Arrays.asList(
"id", "url", "summary", "description", "threat", "recommendations", "workarounds",
"references", "acknowledgements", "keywords", "createDate", "updateDate",
"cvss"
));
}};
/**
* Stores what provider this advisory originates from.
* Must be defined on all advisory instances.
*/
protected AdvisoryTypeIdentifier> sourceIdentifier;
@Getter
protected String summary;
@Getter
protected final List description = new ArrayList<>();
@Getter
@Setter
protected String threat;
@Getter
@Setter
protected String recommendations;
@Getter
@Setter
protected String workarounds;
@Getter
protected final Set acknowledgements = new LinkedHashSet<>();
@Getter
protected final Set references = new LinkedHashSet<>();
@Getter
protected final Set keywords = new LinkedHashSet<>();
@Getter
protected Date createDate;
@Getter
protected Date updateDate;
@Getter
private final CvssVectorSet cvssVectors = new CvssVectorSet();
public final static Comparator UPDATE_CREATE_TIME_COMPARATOR = Comparator
.comparing(AdvisoryEntry::getUpdateDate)
.thenComparing(AdvisoryEntry::getCreateDate);
public AdvisoryEntry(AdvisoryTypeIdentifier> source) {
if (source == null) {
throw new IllegalArgumentException("Advisory source must not be null");
} else {
this.sourceIdentifier = source;
}
}
public AdvisoryEntry(AdvisoryTypeIdentifier> source, String id) {
this(source);
this.id = id;
}
public void setSourceIdentifier(AdvisoryTypeIdentifier> source) {
if (source == null) {
throw new IllegalArgumentException("Advisory source must not be null");
}
if (LOG.isDebugEnabled() && source != this.sourceIdentifier) {
LOG.warn("Explicitly assigned source differs from originally assigned [{}] --> [{}]", this.sourceIdentifier.toExtendedString(), source.toExtendedString());
}
this.sourceIdentifier = source;
}
@Override
public AdvisoryTypeIdentifier> getSourceIdentifier() {
return sourceIdentifier;
}
public AdvisoryEntry setSummary(String summary) {
this.summary = summary;
return this;
}
public void setDescription(DescriptionParagraph paragraph) {
this.description.clear();
if (paragraph.isEmpty()) return;
this.description.add(paragraph);
}
public void addDescription(String title, String content) {
if (StringUtils.hasText(content)) {
this.addDescription(new DescriptionParagraph(title, content));
}
}
public void addDescription(DescriptionParagraph paragraph) {
if (paragraph.isEmpty()) return;
this.description.add(paragraph);
}
public void addDescription(List paragraphs) {
paragraphs.stream()
.filter(p -> !p.isEmpty())
.forEach(this::addDescription);
}
public void clearDescription() {
this.description.clear();
}
public void addReference(Reference reference) {
if (StringUtils.hasText(reference.getUrl())) {
this.references.add(reference);
}
}
public void addReferences(Collection references) {
references.forEach(this::addReference);
}
public void removeReference(String reference) {
this.references.removeIf(r -> r.getUrl().equals(reference));
this.references.removeIf(r -> r.getTitle().equals(reference));
}
public void removeReference(Reference reference) {
this.references.remove(reference);
}
public void addAcknowledgement(String author) {
if (StringUtils.hasText(author)) {
this.acknowledgements.add(author);
}
}
public void addAcknowledgements(Collection authors) {
authors.forEach(this::addAcknowledgement);
}
public void removeAcknowledgement(String author) {
this.acknowledgements.remove(author);
}
public void addKeyword(String keyword) {
if (StringUtils.hasText(keyword)) {
this.keywords.add(keyword);
}
}
public void addKeywords(Collection keywords) {
keywords.forEach(this::addKeyword);
}
public void removeKeyword(String keyword) {
this.keywords.remove(keyword);
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
setCorrectUpdateCreateDateOrder();
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
setCorrectUpdateCreateDateOrder();
}
private void setCorrectUpdateCreateDateOrder() {
if (this.updateDate != null && this.createDate != null &&
this.updateDate.before(this.createDate)) {
final Date tmp = this.updateDate;
this.updateDate = this.createDate;
this.createDate = tmp;
}
}
public void setCreateDate(long creationDate) {
setCreateDate(new Date(creationDate));
}
public void setUpdateDate(long lastModifiedDate) {
setUpdateDate(new Date(lastModifiedDate));
}
public String getTextDescription() {
return description.stream().map(DescriptionParagraph::toString).collect(Collectors.joining("\n\n"));
}
public DescriptionParagraph getDescription(String title) {
return description.stream()
.filter(p -> p.getHeader().equals(title))
.findFirst()
.orElse(null);
}
public String getDescriptionContent(String title) {
final DescriptionParagraph paragraph = getDescription(title);
return paragraph != null ? paragraph.getContent() : null;
}
public boolean hasBeenUpdatedSince(long timestamp) {
final Date date = updateDate != null ? updateDate : createDate;
return date != null && date.getTime() > timestamp;
}
public boolean hasBeenUpdatedBefore(long timestamp) {
final Date date = updateDate != null ? updateDate : createDate;
return date != null && date.getTime() < timestamp;
}
public abstract String getUrl();
public abstract String getType();
/* TYPE CONVERSION METHODS */
@Override
public AdvisoryMetaData constructBaseModel() {
return new AdvisoryMetaData();
}
public static T fromAdvisoryMetaData(AdvisoryMetaData amd, Supplier constructor) {
if (amd == null) return null;
return constructor.get()
.performAction(v -> v.appendFromBaseModel(amd));
}
public static AdvisoryEntry fromAdvisoryMetaData(AdvisoryMetaData amd) {
final AdvisoryTypeIdentifier> foundType = AdvisoryTypeStore.get().fromAdvisoryMetaData(amd).getIdentifier();
return fromAdvisoryMetaData(amd, foundType.getAdvisoryFactory());
}
public static T fromInputMap(Map map, Supplier constructor) {
if (map == null) return null;
return constructor.get()
.performAction(v -> v.appendFromMap(map));
}
public static T fromJson(JSONObject json, Supplier constructor) {
if (json == null) return null;
return fromInputMap(json.toMap(), constructor);
}
public static T fromJson(File file, Supplier constructor) throws IOException {
if (file == null) return null;
final String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
final JSONObject json = new JSONObject(content);
return fromJson(json, constructor);
}
public static AdvisoryEntry fromJson(JSONObject json) {
final ContentIdentifierStore.SingleContentIdentifierParseResult> foundType = AdvisoryTypeStore.get().fromJsonNameAndImplementation(json);
return fromJson(json, foundType.getIdentifier().getAdvisoryFactory());
}
public static T fromDocument(Document document, Supplier constructor) {
if (document == null) return null;
return constructor.get()
.performAction(v -> v.appendFromDocument(document));
}
@Override
public void appendFromBaseModel(AdvisoryMetaData advisoryMetaData) {
super.appendFromBaseModel(advisoryMetaData);
this.setId(advisoryMetaData.get(AdvisoryMetaData.Attribute.NAME));
final String source = advisoryMetaData.get(AdvisoryMetaData.Attribute.SOURCE);
final String sourceImplementation = advisoryMetaData.get(AdvisoryMetaData.Attribute.SOURCE_IMPLEMENTATION);
if (StringUtils.hasText(source) || StringUtils.hasText(sourceImplementation)) {
this.setSourceIdentifier(AdvisoryTypeStore.get().fromNameAndImplementation(source, sourceImplementation));
} else {
AdvisoryTypeStore.get().inferSourceIdentifierFromIdIfAbsent(this);
}
this.setSummary(advisoryMetaData.get(AdvisoryMetaData.Attribute.SUMMARY));
if (StringUtils.hasText(advisoryMetaData.get(AdvisoryMetaData.Attribute.DESCRIPTION))) {
this.addDescription(DescriptionParagraph.fromJson(new JSONArray(advisoryMetaData.get(AdvisoryMetaData.Attribute.DESCRIPTION))));
}
this.setThreat(advisoryMetaData.get(AdvisoryMetaData.Attribute.THREAT));
this.setRecommendations(advisoryMetaData.get(AdvisoryMetaData.Attribute.RECOMMENDATIONS));
this.setWorkarounds(advisoryMetaData.get(AdvisoryMetaData.Attribute.WORKAROUNDS));
if (StringUtils.hasText(advisoryMetaData.get(AdvisoryMetaData.Attribute.ACKNOWLEDGEMENTS))) {
final JSONArray acknowledgements = new JSONArray(advisoryMetaData.get(AdvisoryMetaData.Attribute.ACKNOWLEDGEMENTS));
for (int i = 0; i < acknowledgements.length(); i++) {
this.addAcknowledgement(acknowledgements.optString(i));
}
}
if (StringUtils.hasText(advisoryMetaData.get(AdvisoryMetaData.Attribute.KEYWORDS))) {
final JSONArray keywords = new JSONArray(advisoryMetaData.get(AdvisoryMetaData.Attribute.KEYWORDS));
for (int i = 0; i < keywords.length(); i++) {
this.addKeyword(keywords.optString(i));
}
}
final String referencesString = advisoryMetaData.get(AdvisoryMetaData.Attribute.REFERENCES);
if (StringUtils.hasText(referencesString)) {
final JSONArray references = new JSONArray(referencesString);
for (int i = 0; i < references.length(); i++) {
this.addReference(Reference.fromJson(references.optJSONObject(i)));
}
}
this.setCreateDate(TimeUtils.tryParse(advisoryMetaData.get(AdvisoryMetaData.Attribute.CREATE_DATE)));
this.setUpdateDate(TimeUtils.tryParse(advisoryMetaData.get(AdvisoryMetaData.Attribute.UPDATE_DATE)));
if (StringUtils.hasText(advisoryMetaData.get(AdvisoryMetaData.Attribute.CVSS_VECTORS))) {
final String cvssVectors = advisoryMetaData.get(AdvisoryMetaData.Attribute.CVSS_VECTORS);
this.cvssVectors.addAllCvssVectors(CvssVectorSet.fromJson(new JSONArray(cvssVectors)));
}
}
@Override
public void appendToBaseModel(AdvisoryMetaData amd) {
super.appendToBaseModel(amd);
amd.set(AdvisoryMetaData.Attribute.NAME, id);
amd.set(AdvisoryMetaData.Attribute.URL, getUrl());
amd.set(AdvisoryMetaData.Attribute.TYPE, getType());
amd.set(AdvisoryMetaData.Attribute.SUMMARY, summary);
if (!description.isEmpty()) {
amd.set(AdvisoryMetaData.Attribute.DESCRIPTION, description.stream().map(DescriptionParagraph::toJson).collect(CustomCollectors.toJsonArray()).toString());
}
if (threat != null) amd.set(AdvisoryMetaData.Attribute.THREAT, threat);
if (recommendations != null) amd.set(AdvisoryMetaData.Attribute.RECOMMENDATIONS, recommendations);
if (workarounds != null) amd.set(AdvisoryMetaData.Attribute.WORKAROUNDS, workarounds);
if (!acknowledgements.isEmpty()) {
amd.set(AdvisoryMetaData.Attribute.ACKNOWLEDGEMENTS, new JSONArray(acknowledgements).toString());
}
if (!references.isEmpty()) {
amd.set(AdvisoryMetaData.Attribute.REFERENCES, references.stream().map(Reference::toJson).collect(CustomCollectors.toJsonArray()).toString());
}
if (!keywords.isEmpty()) {
amd.set(AdvisoryMetaData.Attribute.KEYWORDS, new JSONArray(keywords).toString());
}
if (createDate != null) {
amd.set(AdvisoryMetaData.Attribute.CREATE_DATE, Long.toString(this.createDate.getTime()));
amd.set(AdvisoryMetaData.Attribute.CREATE_DATE_FORMATTED, TimeUtils.formatNormalizedDate(this.createDate));
}
if (updateDate != null) {
amd.set(AdvisoryMetaData.Attribute.UPDATE_DATE, Long.toString(this.updateDate.getTime()));
amd.set(AdvisoryMetaData.Attribute.UPDATE_DATE_FORMATTED, TimeUtils.formatNormalizedDate(this.updateDate));
}
if (!this.cvssVectors.isEmpty()) {
amd.set(AdvisoryMetaData.Attribute.CVSS_VECTORS, this.cvssVectors.toJson().toString());
}
}
public Vulnerability toVulnerability() {
final Vulnerability vulnerability = new Vulnerability(id);
vulnerability.setUrl(this.getUrl());
vulnerability.setDescription(firstNonEmpty(this.summary, this.getTextDescription()));
vulnerability.setCreateDate(this.createDate);
vulnerability.setUpdateDate(this.updateDate);
vulnerability.addReferences(new ArrayList<>(this.references));
vulnerability.addReference(Reference.fromTitleAndUrl("Advisor URL", getUrl()));
vulnerability.addReferencedVulnerabilities(this.referencedVulnerabilities);
vulnerability.addReferencedSecurityAdvisories(this.referencedSecurityAdvisories);
vulnerability.getCvssVectors().addAllCvssVectors(this.cvssVectors);
return vulnerability;
}
@Override
public void appendFromDataClass(AdvisoryEntry dataClass) {
super.appendFromDataClass(dataClass);
if (StringUtils.hasText(dataClass.getSummary())) {
this.setSummary(dataClass.getSummary());
}
if (!dataClass.getDescription().isEmpty()) {
this.addDescription(dataClass.getDescription());
}
if (StringUtils.hasText(dataClass.getThreat())) {
this.setThreat(dataClass.getThreat());
}
if (StringUtils.hasText(dataClass.getRecommendations())) {
this.setRecommendations(dataClass.getRecommendations());
}
if (StringUtils.hasText(dataClass.getWorkarounds())) {
this.setWorkarounds(dataClass.getWorkarounds());
}
if (!dataClass.getReferences().isEmpty()) {
this.addReferences(dataClass.getReferences());
}
if (!dataClass.getAcknowledgements().isEmpty()) {
this.addAcknowledgements(dataClass.getAcknowledgements());
}
if (!dataClass.getKeywords().isEmpty()) {
this.addKeywords(dataClass.getKeywords());
}
if (dataClass.getCreateDate() != null) {
this.setCreateDate(dataClass.getCreateDate());
}
if (dataClass.getUpdateDate() != null) {
this.setUpdateDate(dataClass.getUpdateDate());
}
if (!dataClass.getCvssVectors().isEmpty()) {
this.cvssVectors.addAllCvssVectors(dataClass.getCvssVectors());
}
}
@Override
public void appendFromMap(Map input) {
super.appendFromMap(input);
if (input.containsKey("id")) {
this.setId(String.valueOf(input.get("id")));
}
final String source = (String) input.getOrDefault("source", null);
final String sourceImplementation = (String) input.getOrDefault("sourceImplementation", null);
if (source != null || sourceImplementation != null) {
this.setSourceIdentifier(AdvisoryTypeStore.get().fromNameAndImplementation(source, sourceImplementation));
}
this.setSummary((String) input.getOrDefault("summary", null));
final Object inputDescriptionParagraphs = input.getOrDefault("description", new ArrayList<>());
if (inputDescriptionParagraphs instanceof List) {
final List> preTypedDescriptionParagraphs = (List>) inputDescriptionParagraphs;
if (!preTypedDescriptionParagraphs.isEmpty()) {
if (preTypedDescriptionParagraphs.get(0) instanceof DescriptionParagraph) {
final List descriptionParagraphs = (List) inputDescriptionParagraphs;
for (DescriptionParagraph descriptionParagraph : descriptionParagraphs) {
this.addDescription(descriptionParagraph);
}
} else if (preTypedDescriptionParagraphs.get(0) instanceof Map) {
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy