Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ creation), `review` (on transition to `Review`) or `release` (on `AutoPublish`/`
preserving the historic behavior). Once assigned, the final tracking ID never changes again, regardless of
further workflow transitions.
If `CSAF_REFERENCES_BASE_URL` is defined, a JSON reference in `document/references` with the set URL is added when publishing the document.
This self-reference URL includes the document's TLP label (`{baseUrl}/{tlp}/{year}/{trackingId}.json`).
To also add an HTML reference (`.html` variant), set `CSAF_WORKFLOW_CREATE_HTML_REFERENCE=true` (default: `false`).
The variable `CSAF_REFERENCES_REGENERATION` controls when this self-reference is (re)created: `always` (recomputed
at every publish, and also on every advisory update, so a later TLP change is kept in sync, `initial` (default, generated once,
at the first publish, and never updated again), or `never` (auto-generation is skipped entirely).
See **.env.example** for an example configuration.

### Management of engine data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ private void checkConfiguration() {
LOG.info("Is Allowed to Approved Own Documents: {}.", configuration.getWorkflow().isAllowOwnDocumentsApproved());
LOG.info("Creates an Html Reference on Publish: {}.", configuration.getWorkflow().isCreateHtmlReference());
LOG.info("csaf.trackingid.assignment.phase is configured to {}.", advisoryService.getTrackingIdAssignmentPhase());
LOG.info("csaf.references.regeneration is configured to {}.", advisoryService.getSelfRefRegenerationMode());

LOG.info(CONFIG_LOG_SEPARATOR);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -870,12 +870,35 @@ public static boolean timestampIsBefore(String timestamp1, String timestamp2) {
}

/**
* add new node to the document references with category 'sef'
* (Re-)generate the self-reference DocumentReferencesNode(s) from the document's current tracking id and TLP label.
* If a self-reference already exists (matched by summary), its url is updated in place; otherwise it is added.
* @param baseUrl the configured base url
* @param createHtmlReference when {@code true}, an additional DocumentReferencesNode with the HTML url of the tracking id is created/updated
*/
public void generateOrUpdateSelfReference(String baseUrl, boolean createHtmlReference) {

if (baseUrl == null || baseUrl.isBlank()) {
return;
}

String trackingId = getDocumentTrackingId();
String referenceUrl = calculateReferenceUrl(baseUrl, trackingId);
this.addOrUpdateDocumentReferencesNode("JSON URL generated by system", referenceUrl);
if (createHtmlReference) {
String htmlReferenceUrl = calculateHtmlReferenceUrl(baseUrl, trackingId);
this.addOrUpdateDocumentReferencesNode("HTML URL generated by system", htmlReferenceUrl);
}
}

/**
* Add or update a node in the document references with category 'self'.
* If a 'self' reference entry with the same summary already exists, its url is updated.
* Otherwise, a new entry is appended.
* @param summary summary of the node
* @param url url of the node
* @return this wrapper
*/
public AdvisoryWrapper addDocumentReferencesNode(String summary, String url) {
private AdvisoryWrapper addOrUpdateDocumentReferencesNode(String summary, String url) {

ObjectNode documentNode = getOrCreateObjectNode(this.advisoryNode, List.of("csaf", "document"));
ArrayNode referencesNode = (ArrayNode) documentNode.get("references");
Expand All @@ -885,9 +908,21 @@ public AdvisoryWrapper addDocumentReferencesNode(String summary, String url) {
documentNode.set("references", referencesNode);
}

// either update existing ref node
for (JsonNode existingEntry : referencesNode) {
JsonNode categoryNode = existingEntry.at("/category");
JsonNode summaryNode = existingEntry.at("/summary");
if (existingEntry.isObject()
&& !categoryNode.isMissingNode() && "self".equals(categoryNode.asString())
&& !summaryNode.isMissingNode() && summary.equals(summaryNode.asString())) {
((ObjectNode) existingEntry).put("url", url);
return this;
}
}

// ... or add new ref node
ObjectNode entry = referencesNode.addObject();
entry.put("category", "self");

entry.put("summary", summary);
entry.put("url", url);
return this;
Expand All @@ -901,14 +936,12 @@ public void setTemporaryTrackingId(String trackingidCompany, String trackingidDi
}

/**
* Set the final tracking id in the advisory and a DocumentReferencesNode with the url of the tracking id.
* @param baseUrl the configured base url
* Set the final tracking id in the advisory.
* @param trackingIdCompany the configured company for the name of the tracking id
* @param trackingIdDigits the count of leading zeros to which the sequentialNumber is filled with
* @param sequentialNumber the next sequentialNumber
* @param createHtmlReference when {@code true}, an additional DocumentReferencesNode with the HTML url of the tracking id is created
*/
public void setFinalTrackingIdAndUrl(String baseUrl, String trackingIdCompany, String trackingIdDigits, long sequentialNumber, boolean createHtmlReference) {
public void setFinalTrackingId(String trackingIdCompany, String trackingIdDigits, long sequentialNumber) {

setTempTrackingIdInMeta(getDocumentTrackingId());

Expand All @@ -917,15 +950,6 @@ public void setFinalTrackingIdAndUrl(String baseUrl, String trackingIdCompany, S
int year = calculatePublishYear();
String trackingId = companyName + "-" + year + "-" + formatted;
setDocumentTrackingId(trackingId);

if (baseUrl != null && !baseUrl.isBlank()) {
String referenceUrl = calculateReferenceUrl(baseUrl, trackingId);
this.addDocumentReferencesNode("JSON URL generated by system", referenceUrl);
if (createHtmlReference) {
String htmlReferenceUrl = calculateHtmlReferenceUrl(baseUrl, trackingId);
this.addDocumentReferencesNode("HTML URL generated by system", htmlReferenceUrl);
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.bsi.secvisogram.csaf_cms_backend.model;

/**
* Controls when the auto-generated self-reference (document/references, category "self")
* is created or updated.
*/
public enum SelfRefRegenerationMode {
ALWAYS,
INITIAL,
NEVER
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,18 @@ public class AdvisoryService {

private TrackingIdAssignmentPhase trackingIdAssignmentPhase;

@Value("${csaf.references.regeneration}")
private String selfRefRegenerationModeValue;

private SelfRefRegenerationMode selfRefRegenerationMode;

@Autowired
private CsafConfiguration configuration;

@PostConstruct
void validateTrackingIdAssignmentPhase() {
try {
this.trackingIdAssignmentPhase = TrackingIdAssignmentPhase.valueOf(
this.trackingIdAssignmentPhaseValue.trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
LOG.error("Invalid value '{}' for property csaf.trackingid.assignment.phase. "
+ "Allowed values are: {}. Falling back to default value '{}'.",
this.trackingIdAssignmentPhaseValue, Arrays.toString(TrackingIdAssignmentPhase.values()),
TrackingIdAssignmentPhase.RELEASE);
this.trackingIdAssignmentPhase = TrackingIdAssignmentPhase.RELEASE;
}
this.trackingIdAssignmentPhase = parseEnumProperty("csaf.trackingid.assignment.phase",
this.trackingIdAssignmentPhaseValue, TrackingIdAssignmentPhase.class, TrackingIdAssignmentPhase.RELEASE);
}

/**
Expand All @@ -124,6 +121,41 @@ public TrackingIdAssignmentPhase getTrackingIdAssignmentPhase() {
return this.trackingIdAssignmentPhase;
}

@PostConstruct
void validateSelfReferenceRegenerationMode() {
this.selfRefRegenerationMode = parseEnumProperty("csaf.references.regeneration",
this.selfRefRegenerationModeValue, SelfRefRegenerationMode.class, SelfRefRegenerationMode.INITIAL);
}

/**
* Parse a String-valued configuration property into an enum constant, falling back to a default value if the
* property does not match any constant.
*
* @param propertyName the name of the configuration property, for logging
* @param rawValue the raw, unparsed property value
* @param enumType the enum type to parse into
* @param fallback the value to fall back to
* @return the parsed enum constant, or {@code fallback} if the raw value is invalid
*/
private static <E extends Enum<E>> E parseEnumProperty(String propertyName, String rawValue, Class<E> enumType, E fallback) {
try {
return Enum.valueOf(enumType, rawValue.trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
LOG.error("Invalid value '{}' for property {}. Allowed values are: {}. Falling back to default value '{}'.",
rawValue, propertyName, Arrays.toString(enumType.getEnumConstants()), fallback);
return fallback;
}
}

/**
* get the self-reference regeneration mode
*
* @return the self-reference regeneration mode
*/
public SelfRefRegenerationMode getSelfRefRegenerationMode() {
return this.selfRefRegenerationMode;
}

@Autowired
private BuildProperties buildProperties;

Expand Down Expand Up @@ -253,7 +285,7 @@ IdAndRevision addAdvisoryForCredentials(CreateAdvisoryRequest newCsafJson, Authe
}

if (this.trackingIdAssignmentPhase == TrackingIdAssignmentPhase.DRAFT) {
setFinalTrackingIdAndUrl(newAdvisoryNode);
setFinalTrackingId(newAdvisoryNode);
} else {
addTemporaryTrackingId(newAdvisoryNode);
}
Expand Down Expand Up @@ -520,6 +552,12 @@ public String updateAdvisory(String advisoryId, String revision, CreateAdvisoryR
newAdvisoryNode.editLastRevisionHistoryElement(changedCsafJson, timestampNow);
}

// goal: always but avoid generating for non-published documents.
if (this.selfRefRegenerationMode == SelfRefRegenerationMode.ALWAYS
&& oldAdvisoryNode.getLastMajorVersion() >= 1) {
regenerateSelfReference(newAdvisoryNode);
}

String result = this.couchDbService.updateDocument(newAdvisoryNode.advisoryAsString());

AuditTrailWrapper auditTrail = AdvisoryAuditTrailDiffWrapper.createNewFromAdvisories(oldAdvisoryNode, newAdvisoryNode)
Expand All @@ -534,6 +572,24 @@ public String updateAdvisory(String advisoryId, String revision, CreateAdvisoryR
}
}

private boolean isCreateHtmlReference() {
return this.configuration.getWorkflow() != null && this.configuration.getWorkflow().isCreateHtmlReference();
}

private void regenerateSelfReference(AdvisoryWrapper advisoryNode) {
advisoryNode.generateOrUpdateSelfReference(this.referencesBaseUrl, isCreateHtmlReference());
}

private AdvisoryWrapper finalizePublication(AdvisoryWrapper advisoryNode, String proposedTime) throws CsafException, IOException {
AdvisoryWrapper releaseReadyNode = createReleaseReadyAdvisoryAndValidate(advisoryNode, proposedTime);
setFinalTrackingId(releaseReadyNode);
if (this.selfRefRegenerationMode == SelfRefRegenerationMode.ALWAYS
|| (this.selfRefRegenerationMode == SelfRefRegenerationMode.INITIAL && releaseReadyNode.getLastMajorVersion() < 1)) {
regenerateSelfReference(releaseReadyNode);
}
return releaseReadyNode;
}

/**
* Manually assign the final tracking id for an advisory, if none has been assigned yet.
*
Expand Down Expand Up @@ -564,7 +620,7 @@ public String assignTrackingId(String advisoryId, String revision) throws IOExce
}

AdvisoryWrapper oldAdvisoryNode = AdvisoryWrapper.createCopy(existingAdvisoryNode);
setFinalTrackingIdAndUrl(existingAdvisoryNode);
setFinalTrackingId(existingAdvisoryNode);
existingAdvisoryNode.setRevision(revision);

String newRevision = this.couchDbService.updateDocument(existingAdvisoryNode.advisoryAsString());
Expand Down Expand Up @@ -739,7 +795,7 @@ public String changeAdvisoryWorkflowState(String advisoryId, String revision, Wo

if (newWorkflowState == WorkflowState.Review
&& this.trackingIdAssignmentPhase == TrackingIdAssignmentPhase.REVIEW) {
setFinalTrackingIdAndUrl(existingAdvisoryNode);
setFinalTrackingId(existingAdvisoryNode);
}

if (newWorkflowState == WorkflowState.RfPublication) {
Expand All @@ -765,14 +821,12 @@ public String changeAdvisoryWorkflowState(String advisoryId, String revision, Wo
}
//TODO: Check, if further checks for upload are needed

existingAdvisoryNode = createReleaseReadyAdvisoryAndValidate(existingAdvisoryNode, proposedTime);
setFinalTrackingIdAndUrl(existingAdvisoryNode);
existingAdvisoryNode = finalizePublication(existingAdvisoryNode, proposedTime);
}

if (newWorkflowState == WorkflowState.Published && (previousWorkflowState != WorkflowState.AutoPublish)) {

existingAdvisoryNode = createReleaseReadyAdvisoryAndValidate(existingAdvisoryNode, proposedTime);
setFinalTrackingIdAndUrl(existingAdvisoryNode);

existingAdvisoryNode = finalizePublication(existingAdvisoryNode, proposedTime);
}

AuditTrailWrapper auditTrail = AdvisoryAuditTrailWorkflowWrapper.createNewFrom(newWorkflowState, previousWorkflowState)
Expand All @@ -791,21 +845,19 @@ public String changeAdvisoryWorkflowState(String advisoryId, String revision, Wo
}

/**
* Set the final tracking id in the advisory and a DocumentReferencesNode with the url of the tracking id
* Set the final tracking id in the advisory, unless a final tracking id is already assigned.
*
* @param advisoryNode the node to set the tracking id
* @throws CsafException error creating counter
*/
void setFinalTrackingIdAndUrl(AdvisoryWrapper advisoryNode) throws CsafException {
void setFinalTrackingId(AdvisoryWrapper advisoryNode) throws CsafException {

if (advisoryNode.isFinalTrackingIdAssigned()) {
return;
}

final long sequentialNumber = getNewTrackingIdCounter(TrackingIdCounter.FINAL_OBJECT_ID);
final boolean createHtmlReference = this.configuration.getWorkflow() != null
&& this.configuration.getWorkflow().isCreateHtmlReference();
advisoryNode.setFinalTrackingIdAndUrl(this.referencesBaseUrl, this.trackingidCompany, this.trackingidDigits, sequentialNumber, createHtmlReference);
advisoryNode.setFinalTrackingId(this.trackingidCompany, this.trackingidDigits, sequentialNumber);
}


Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ csaf.trackingid.digits=${CSAF_TRACKINGID_DIGITS:}
# Point in the workflow status at which the final tracking id (instead of a temporary -TEMP- id) is assigned.
# One of: draft, review, release (default).
csaf.trackingid.assignment.phase=${CSAF_TRACKINGID_ASSIGNMENT_PHASE:release}
# Controls when the auto-generated self-reference (document/references, category "self") is (re)created.
# - always: generated at publish time and then being kept updated based on the value of its components (TLP)
# - initial (default): generated once, at the first publish, and never updated again.
# - never: never auto-generated.
csaf.references.regeneration=${CSAF_REFERENCES_REGENERATION:initial}


csaf.autoPublish.enabled=${CSAF_AUTOPUBLISH_ENABLED:true}
Expand Down
Loading
Loading