Skip to content

Commit

Permalink
Adds workspace scope validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrowndotje committed Nov 24, 2023
1 parent ad04db6 commit 7df29db
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.structurizr.configuration.Role;
import com.structurizr.configuration.Visibility;
import com.structurizr.configuration.WorkspaceConfiguration;
import com.structurizr.configuration.WorkspaceScope;
import com.structurizr.encryption.AesEncryptionStrategy;
import com.structurizr.encryption.EncryptedWorkspace;
import com.structurizr.encryption.EncryptionLocation;
Expand All @@ -20,6 +21,8 @@
import com.structurizr.onpremises.util.StructurizrProperties;
import com.structurizr.util.StringUtils;
import com.structurizr.util.WorkspaceUtils;
import com.structurizr.validation.WorkspaceScopeValidationException;
import com.structurizr.validation.WorkspaceScopeValidatorFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Expand Down Expand Up @@ -224,6 +227,11 @@ public long createWorkspace(User user) throws WorkspaceComponentException {
NumberFormat format = new DecimalFormat("0000");

Workspace workspace = new Workspace("Workspace " + format.format(workspaceId), "Description");

if (Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_SCOPE_VALIDATION)) {
workspace.getConfiguration().setScope(WorkspaceScope.SoftwareSystem);
}

String json = WorkspaceUtils.toJson(workspace, false);

putWorkspace(workspaceId, json);
Expand Down Expand Up @@ -303,6 +311,9 @@ public void putWorkspace(long workspaceId, String json) throws WorkspaceComponen
jsonToBeStored = stringWriter.toString();
} else {
Workspace workspace = WorkspaceUtils.fromJson(json);

validateWorkspaceScope(workspace);

workspace.setId(workspaceId);
workspace.setLastModifiedDate(DateUtils.removeMilliseconds(DateUtils.getNow()));

Expand Down Expand Up @@ -396,6 +407,18 @@ public void putWorkspace(long workspaceId, String json) throws WorkspaceComponen
}
}

private void validateWorkspaceScope(Workspace workspace) throws WorkspaceScopeValidationException {
// if workspace scope validation is enabled, reject workspaces without a defined scope
if (Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_SCOPE_VALIDATION)) {
if (workspace.getConfiguration().getScope() == null) {
throw new WorkspaceScopeValidationException("Strict workspace scope validation has been enabled on this on-premises installation, but this workspace has no defined scope - see https://docs.structurizr.com/workspaces for more information.");
}
}

// validate workspace scope
WorkspaceScopeValidatorFactory.getValidator(workspace).validate(workspace);
}

private WorkspaceEvent createWorkspaceEvent(WorkspaceMetaData workspaceMetaData, String workspaceAsJson) {
return new WorkspaceEvent() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public class Configuration extends ConfigLookup {

private static final String PLUGINS_DIRECTORY_NAME = "plugins";

private static final String WORKSPACE_SCOPE_STRICT = "strict";
private static final String WORKSPACE_SCOPE_RELAXED = "relaxed";

private File dataDirectory;
private String webUrl;
private Set<String> adminUsersAndRoles = new HashSet<>();
Expand Down Expand Up @@ -63,6 +66,7 @@ private Configuration() {
features.put(Features.UI_WORKSPACE_SETTINGS, Boolean.parseBoolean(getConfigurationParameterFromStructurizrPropertiesFile(Features.UI_WORKSPACE_SETTINGS, "true")));
features.put(Features.UI_DSL_EDITOR, Boolean.parseBoolean(getConfigurationParameterFromStructurizrPropertiesFile(Features.UI_DSL_EDITOR, "false")));
features.put(Features.WORKSPACE_ARCHIVING, Boolean.parseBoolean(getConfigurationParameterFromStructurizrPropertiesFile(Features.WORKSPACE_ARCHIVING, "false")));
features.put(Features.WORKSPACE_SCOPE_VALIDATION, getConfigurationParameterFromStructurizrPropertiesFile(Features.WORKSPACE_SCOPE_VALIDATION, "relaxed").equalsIgnoreCase("strict"));
features.put(Features.DIAGRAM_REVIEWS, Boolean.parseBoolean(getConfigurationParameterFromStructurizrPropertiesFile(Features.DIAGRAM_REVIEWS, "true")));

// for backwards compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class Features {
public static final String UI_WORKSPACE_SETTINGS = "structurizr.feature.ui.workspaceSettings";

public static final String WORKSPACE_ARCHIVING = "structurizr.feature.workspace.archiving";
public static final String WORKSPACE_SCOPE_VALIDATION = "structurizr.feature.workspace.scope";

public static final String DIAGRAM_REVIEWS = "structurizr.feature.diagramReviews";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public void uncaughtException(Thread t, Throwable ex) {
log.info("Data storage: " + Configuration.getInstance().getDataStorageImplementationName());
log.info("Caching: " + Configuration.getInstance().getCacheImplementationName());
log.info("Workspace archiving: " + Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_ARCHIVING));
log.info("Workspace scope: " + (Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_SCOPE_VALIDATION) ? "strict" : "relaxed"));
log.info("Search: " + Configuration.getInstance().getSearchImplementationName());

if (Configuration.getInstance().getWorkspaceEventListener() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.structurizr.Workspace;
import com.structurizr.configuration.Role;
import com.structurizr.configuration.Visibility;
import com.structurizr.configuration.WorkspaceScope;
import com.structurizr.encryption.AesEncryptionStrategy;
import com.structurizr.encryption.EncryptedWorkspace;
import com.structurizr.encryption.EncryptionLocation;
Expand Down Expand Up @@ -811,4 +812,48 @@ public void putWorkspaceMetaData(WorkspaceMetaData wmd) {
assertEquals("", workspaceMetaData.getSharingToken());
}

@Test
public void test_putWorkspace_ThrowsAnException_WhenWorkspaceScopeValidationIsStrictAndTheWorkspaceIsUnscoped() throws Exception {
Workspace workspace = new Workspace("Name", "Description");
workspace.getConfiguration().setScope(null);

String json = WorkspaceUtils.toJson(workspace, false);

final WorkspaceMetaData wmd = new WorkspaceMetaData(1);
wmd.setPublicWorkspace(false);

WorkspaceDao dao = new MockWorkspaceDao();

WorkspaceComponent workspaceComponent = new WorkspaceComponentImpl(dao, "");
try {
Configuration.getInstance().setFeatureEnabled(Features.WORKSPACE_SCOPE_VALIDATION);
workspaceComponent.putWorkspace(1, json);
fail();
} catch (WorkspaceComponentException e) {
assertEquals("Strict workspace scope validation has been enabled on this on-premises installation, but this workspace has no defined scope - see https://docs.structurizr.com/workspaces for more information.", e.getMessage());
}
}

@Test
public void test_putWorkspace_ThrowsAnException_WhenWorkspaceScopeValidationFails() throws Exception {
Workspace workspace = new Workspace("Name", "Description");
workspace.getConfiguration().setScope(WorkspaceScope.Landscape);
workspace.getModel().addSoftwareSystem("A").addContainer("AA");

String json = WorkspaceUtils.toJson(workspace, false);

final WorkspaceMetaData wmd = new WorkspaceMetaData(1);
wmd.setPublicWorkspace(false);

WorkspaceDao dao = new MockWorkspaceDao();

WorkspaceComponent workspaceComponent = new WorkspaceComponentImpl(dao, "");
try {
workspaceComponent.putWorkspace(1, json);
fail();
} catch (WorkspaceComponentException e) {
assertEquals("Workspace is landscape scoped, but the software system named A has containers.", e.getMessage());
}
}

}

0 comments on commit 7df29db

Please sign in to comment.