Skip to content
Draft
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
40 changes: 40 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,47 @@ services:
ports:
- "$CSAF_VALIDATOR_PORT:8082"

csaf-trusted-provider:
build:
context: ./trusted-provider
container_name: csaf-trusted-provider
env_file: .env
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
volumes:
- ./trusted-provider/config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./trusted-provider/config:/config
- ./trusted-provider/data:/data
ports:
- "9080:80"

# Run this manually to initialize CSAF provider
init-provider:
build:
context: ./uploader
profiles: [ "run_manually" ]
depends_on:
- csaf-trusted-provider
environment:
OPTIONS: "--config=/data/config-create.ini"
volumes:
- ./uploader:/data

# Run this manually to initialize CSAF provider
init-cms-backend-db:
image: curlimages/curl:7.85.0
depends_on:
- csaf-couchdb
profiles: [ "run_manually" ]
command: -u ${CSAF_COUCHDB_USER}:${CSAF_COUCHDB_PASSWORD} -X PUT ${CSAF_COUCHDB_HOST}:${CSAF_COUCHDB_PORT}/${CSAF_COUCHDB_DATABASE}

hoppscotch:
image: hoppscotch/hoppscotch:latest
ports:
- 3000:3000

volumes:
csaf-couchdb-data:
driver: local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package de.bsi.secvisogram.csaf_cms_backend.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class CsafAutoPublishConfiguration {
private boolean enabled = false;
private boolean enableInsecureTLS = false;
private String url = "";
private String password = "";
private String cron = "0 * * * * *";

public boolean isEnabled() {
return enabled;
}

public CsafAutoPublishConfiguration setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}

public String getUrl() {
return url;
}

public CsafAutoPublishConfiguration setUrl(String url) {
this.url = url;
return this;
}

public String getPassword() {
return password;
}

public CsafAutoPublishConfiguration setPassword(String password) {
this.password = password;
return this;
}

public String getCron() {
return cron;
}

public CsafAutoPublishConfiguration setCron(String cron) {
this.cron = cron;
return this;
}

public boolean isEnableInsecureTLS() {
return enableInsecureTLS;
}

public CsafAutoPublishConfiguration setEnableInsecureTLS(boolean enableInsecureTLS) {
this.enableInsecureTLS = enableInsecureTLS;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public class CsafConfiguration {

private CsafSummaryConfiguration summary;
private CsafVersioningConfiguration versioning;

private CsafAutoPublishConfiguration autoPublish;

public CsafSummaryConfiguration getSummary() {
return summary;
}
Expand All @@ -27,4 +28,12 @@ public CsafConfiguration setVersioning(CsafVersioningConfiguration versioning) {
this.versioning = versioning;
return this;
}

public CsafAutoPublishConfiguration getAutoPublish() {
return autoPublish;
}

public void setAutoPublish(CsafAutoPublishConfiguration autoPublish) {
this.autoPublish = autoPublish;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ public static JsonNode applyJsonPatchToNode(JsonNode patch, JsonNode source) {
* @param timestamp2 the second timestamp
* @return true if timestamp1 is chronologically before timestamp2, false otherwise
*/
private static boolean timestampIsBefore(String timestamp1, String timestamp2) {
public static boolean timestampIsBefore(String timestamp1, String timestamp2) {
LocalDateTime t1 = from(ISO_DATE_TIME.parse(timestamp1));
LocalDateTime t2 = from(ISO_DATE_TIME.parse(timestamp2));
return t1.isBefore(t2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public enum WorkflowState {
Draft,
Review,
Approved,

RfPublication,
AutoPublish,
Published
}
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,63 @@ public ResponseEntity<String> setWorkflowStateToRfPublication(
return changeWorkflowState(advisoryId, revision, WorkflowState.RfPublication, proposedTime, null);
}

/**
* Change workflow state of a CSAF document to AutoPublish
*
* @param advisoryId advisoryId id of the CSAF document to change
* @param revision optimistic locking revision
* @param proposedTime optimistic locking revision
* @param documentTrackingStatus the new Document Tracking Status of the CSAF Document
* @return new optimistic locking revision
*/
@Operation(summary = "Change workflow state of an advisory to Published.",
tags = {"Advisory"},
description = "Change the workflow state of the advisory with the given id to Published.")
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Workflow state changed to Publication.",
content = {
@Content(mediaType = MediaType.TEXT_PLAIN_VALUE)
}
),
@ApiResponse(
responseCode = "400",
description = "Advisory ID not found."
),
@ApiResponse(
responseCode = "401",
description = "Unauthorized access to change workflow state."
),
@ApiResponse(
responseCode = "422",
description = "Invalid formatted advisory."
),
@ApiResponse(
responseCode = "500",
description = "Error during process the advisory."
)
})
@PatchMapping("/{advisoryId}/workflowstate/AutoPublish")
public ResponseEntity<String> setWorkflowStateToAutoPublish(
@PathVariable
@Parameter(in = ParameterIn.PATH, description = "The ID of the advisory to change the workflow state of.")
String advisoryId,
@RequestParam @Parameter(description = "Optimistic locking revision.")
String revision,
@RequestParam(required = true)
@Parameter(description = "Proposed Time at which the publication should take place as ISO-8601 UTC string.")
String proposedTime,
@RequestParam
@Parameter(description = "The new Document Tracking Status of the CSAF Document." +
" Only Interim and Final are allowed.")
DocumentTrackingStatus documentTrackingStatus
) throws IOException {
LOG.debug("setWorkflowStateToPublish");
checkValidUuid(advisoryId);
return changeWorkflowState(advisoryId, revision, WorkflowState.AutoPublish, proposedTime, documentTrackingStatus);
}

/**
* Change workflow state of a CSAF document to Published
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ibm.cloud.sdk.core.service.exception.BadRequestException;
import com.ibm.cloud.sdk.core.service.exception.NotFoundException;
import com.ibm.icu.text.SimpleDateFormat;
import de.bsi.secvisogram.csaf_cms_backend.config.CsafConfiguration;
import de.bsi.secvisogram.csaf_cms_backend.config.CsafRoles;
import de.bsi.secvisogram.csaf_cms_backend.couchdb.*;
Expand Down Expand Up @@ -615,7 +616,15 @@ public String changeAdvisoryWorkflowState(String advisoryId, String revision, Wo
// In this step we only want to check if the document would be valid if published but not change it yet.
createReleaseReadyAdvisoryAndValidate(existingAdvisoryNode, proposedTime);
}


if (newWorkflowState == WorkflowState.AutoPublish) {
if (proposedTime == null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.000000000Z");
proposedTime = sdf.format(new Date());
}
existingAdvisoryNode.setDocumentTrackingCurrentReleaseDate(proposedTime);
}

if (newWorkflowState == WorkflowState.Published) {
existingAdvisoryNode = createReleaseReadyAdvisoryAndValidate(existingAdvisoryNode, proposedTime);
if (existingAdvisoryNode.getLastMajorVersion() < 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,15 @@ static boolean canChangeWorkflow(String userToCheck, WorkflowState oldWorkflowSt
if (oldWorkflowState == WorkflowState.RfPublication && newWorkflowState == WorkflowState.Published) {
canBeChanged = hasRole(PUBLISHER, credentials);
}

if (oldWorkflowState == WorkflowState.RfPublication && newWorkflowState == WorkflowState.AutoPublish) {
canBeChanged = hasRole(PUBLISHER, credentials);
}

if (oldWorkflowState == WorkflowState.AutoPublish && newWorkflowState == WorkflowState.Published) {
canBeChanged = hasRole(PUBLISHER, credentials);
}

return canBeChanged;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package de.bsi.secvisogram.csaf_cms_backend.task;

import de.bsi.secvisogram.csaf_cms_backend.config.CsafConfiguration;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

@Configuration
@EnableScheduling
@ComponentScan(basePackages = {"de.bsi.secvisogram.csaf_cms_backend.task"})
public class PublishConfig implements SchedulingConfigurer {

@Autowired
private CsafConfiguration configuration;
private static final Logger LOG = LoggerFactory.getLogger(PublishConfig.class);

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (this.configuration.getAutoPublish() != null) {
if (this.configuration.getAutoPublish().isEnabled()) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addCronTask(task(), this.configuration.getAutoPublish().getCron());
LOG.info("Autopublish activated. Task created with " + this.configuration.getAutoPublish().getCron());
}
}
}

private SecurityContext createSchedulerSecurityContext() {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_publisher", "ROLE_registred");
Authentication authentication = new UsernamePasswordAuthenticationToken(
"PublisherTask",
"Publisher",
authorities
);
context.setAuthentication(authentication);

return context;
}

@Bean
Runnable task() {
return new PublishJob();
}

@Bean
Executor taskExecutor() {
ScheduledThreadPoolExecutor delegateExecutor = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("Publish-Job-%d").build());
SecurityContext schedulerContext = createSchedulerSecurityContext();
return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
}
}
Loading