├── gradle.properties ├── structurizr-onpremises-plugin ├── gradle.properties ├── src │ └── main │ │ └── java │ │ └── com │ │ └── structurizr │ │ └── onpremises │ │ └── component │ │ └── workspace │ │ ├── WorkspaceEventListener.java │ │ ├── WorkspaceEvent.java │ │ └── WorkspaceProperties.java └── build.gradle ├── settings.gradle ├── structurizr-onpremises ├── src │ ├── main │ │ ├── webapp │ │ │ ├── WEB-INF │ │ │ │ ├── fragments │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── auto-save.jspf │ │ │ │ │ │ └── auto-refresh.jspf │ │ │ │ │ ├── dashboard-workspaces-page-control.jspf │ │ │ │ │ ├── coda.jspf │ │ │ │ │ └── diagrams │ │ │ │ │ │ └── publish.jspf │ │ │ │ ├── jboss-web.xml │ │ │ │ ├── raw-views │ │ │ │ │ ├── json.jsp │ │ │ │ │ └── plaintext.jsp │ │ │ │ ├── views │ │ │ │ │ ├── feature-not-available.jsp │ │ │ │ │ ├── public-dsl-editor-disabled.jsp │ │ │ │ │ ├── home.jsp │ │ │ │ │ ├── workspace-could-not-be-locked.jsp │ │ │ │ │ ├── dashboard.jsp │ │ │ │ │ ├── workspace-is-client-side-encrypted.jsp │ │ │ │ │ ├── workspace-is-readonly.jsp │ │ │ │ │ ├── dsl-editor-disabled.jsp │ │ │ │ │ ├── workspace-branches-not-enabled.jsp │ │ │ │ │ ├── user-profile.jsp │ │ │ │ │ ├── workspace-locked.jsp │ │ │ │ │ ├── images.jsp │ │ │ │ │ └── reviews.jsp │ │ │ │ ├── glassfish-web.xml │ │ │ │ ├── applicationContext-session-local.xml │ │ │ │ ├── applicationContext-security.xml │ │ │ │ ├── applicationContext.xml │ │ │ │ ├── applicationContext-security-saml.xml │ │ │ │ ├── applicationContext-session-redis.xml │ │ │ │ ├── applicationContext-security-ldap.xml │ │ │ │ ├── applicationContext-security-inmemory.xml │ │ │ │ ├── root-servlet.xml │ │ │ │ └── applicationContext-security-file.xml │ │ │ └── favicon.ico │ │ ├── java │ │ │ └── com │ │ │ │ └── structurizr │ │ │ │ └── onpremises │ │ │ │ ├── domain │ │ │ │ ├── AuthenticationMethod.java │ │ │ │ ├── review │ │ │ │ │ ├── ReviewType.java │ │ │ │ │ ├── CommentType.java │ │ │ │ │ ├── Diagram.java │ │ │ │ │ ├── Comment.java │ │ │ │ │ └── Session.java │ │ │ │ ├── MessageType.java │ │ │ │ ├── Role.java │ │ │ │ ├── UserType.java │ │ │ │ ├── InputStreamAndContentLength.java │ │ │ │ ├── Image.java │ │ │ │ ├── Messages.java │ │ │ │ └── Message.java │ │ │ │ ├── configuration │ │ │ │ ├── StructurizrEnvironmentVariables.java │ │ │ │ ├── Configurer.java │ │ │ │ ├── AzureBlobStorageConfigurer.java │ │ │ │ ├── AmazonWebServicesS3Configurer.java │ │ │ │ ├── PropertyPlaceholderConfigurer.java │ │ │ │ ├── Features.java │ │ │ │ ├── SamlConfigurer.java │ │ │ │ ├── StructurizrDataDirectory.java │ │ │ │ ├── DefaultsConfigurer.java │ │ │ │ ├── ElasticsearchConfigurer.java │ │ │ │ └── RedisConfigurer.java │ │ │ │ ├── util │ │ │ │ ├── RandomGuidGenerator.java │ │ │ │ ├── JsonUtils.java │ │ │ │ ├── EarlyAccessFeaturesNotAvailableException.java │ │ │ │ ├── HtmlUtils.java │ │ │ │ ├── WorkspaceValidationUtils.java │ │ │ │ ├── AmazonS3ClientUtils.java │ │ │ │ ├── Version.java │ │ │ │ └── DateUtils.java │ │ │ │ ├── component │ │ │ │ ├── workspace │ │ │ │ │ ├── WorkspaceMetadataCache.java │ │ │ │ │ ├── WorkspaceComponentException.java │ │ │ │ │ ├── component.xml │ │ │ │ │ ├── NoOpWorkspaceMetadataCache.java │ │ │ │ │ ├── WorkspaceLockResponse.java │ │ │ │ │ ├── WorkspaceBranch.java │ │ │ │ │ ├── WorkspaceVersion.java │ │ │ │ │ ├── WorkspaceDao.java │ │ │ │ │ ├── AbstractWorkspaceDao.java │ │ │ │ │ ├── JCacheWorkspaceMetadataCache.java │ │ │ │ │ ├── LocalWorkspaceMetadataCache.java │ │ │ │ │ └── WorkspaceComponent.java │ │ │ │ ├── search │ │ │ │ │ ├── DocumentType.java │ │ │ │ │ ├── SearchComponentException.java │ │ │ │ │ ├── component.xml │ │ │ │ │ ├── SearchComponent.java │ │ │ │ │ ├── NoOpSearchComponentImpl.java │ │ │ │ │ ├── SearchResponse.java │ │ │ │ │ ├── Document.java │ │ │ │ │ ├── SearchResult.java │ │ │ │ │ ├── SearchComponentImpl.java │ │ │ │ │ └── AbstractSearchComponentImpl.java │ │ │ │ └── review │ │ │ │ │ ├── ReviewException.java │ │ │ │ │ ├── ReviewComponentException.java │ │ │ │ │ ├── component.xml │ │ │ │ │ ├── FileTypeAndContent.java │ │ │ │ │ ├── ReviewComponent.java │ │ │ │ │ └── ReviewDao.java │ │ │ │ └── web │ │ │ │ ├── api │ │ │ │ ├── ApiException.java │ │ │ │ ├── HttpHeaders.java │ │ │ │ ├── HmacContent.java │ │ │ │ ├── HttpUnauthorizedException.java │ │ │ │ ├── WorkspacesApiResponse.java │ │ │ │ ├── Md5Digest.java │ │ │ │ ├── ApiResponse.java │ │ │ │ ├── HashBasedMessageAuthenticationCode.java │ │ │ │ ├── HmacAuthorizationHeader.java │ │ │ │ └── WorkspaceApiResponse.java │ │ │ │ ├── RawViewResolver.java │ │ │ │ ├── NoOpSpringSessionRepositoryFilter.java │ │ │ │ ├── error │ │ │ │ ├── Http404Controller.java │ │ │ │ └── Http500Controller.java │ │ │ │ ├── security │ │ │ │ ├── CsrfSecurityRequestMatcher.java │ │ │ │ ├── BcryptController.java │ │ │ │ ├── AuthenticationFailureHandler.java │ │ │ │ ├── SignoutController.java │ │ │ │ ├── AuthenticationSuccessHandler.java │ │ │ │ └── SignInController.java │ │ │ │ ├── user │ │ │ │ └── UserProfileController.java │ │ │ │ ├── workspace │ │ │ │ ├── dsl │ │ │ │ │ ├── DslEditorResponse.java │ │ │ │ │ └── DslController.java │ │ │ │ ├── management │ │ │ │ │ ├── CreateWorkspaceController.java │ │ │ │ │ ├── ShareWorkspaceController.java │ │ │ │ │ ├── WorkspaceSettingsController.java │ │ │ │ │ ├── UnshareWorkspaceController.java │ │ │ │ │ ├── PublicWorkspaceController.java │ │ │ │ │ ├── PrivateWorkspaceController.java │ │ │ │ │ ├── DeleteBranchController.java │ │ │ │ │ └── DeleteWorkspaceController.java │ │ │ │ ├── explore │ │ │ │ │ ├── ExploreController.java │ │ │ │ │ ├── ModelController.java │ │ │ │ │ ├── TreeController.java │ │ │ │ │ └── GraphController.java │ │ │ │ ├── diagrams │ │ │ │ │ └── DiagramEditorController.java │ │ │ │ ├── AbstractWorkspaceEditorController.java │ │ │ │ ├── json │ │ │ │ │ └── JsonController.java │ │ │ │ └── images │ │ │ │ │ └── ImagesController.java │ │ │ │ └── home │ │ │ │ └── PaginatedWorkspaceList.java │ │ └── resources │ │ │ └── log4j2.properties │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── structurizr │ │ │ └── onpremises │ │ │ ├── web │ │ │ ├── api │ │ │ │ ├── HmacContentTests.java │ │ │ │ ├── Md5DigestTests.java │ │ │ │ ├── HashBasedMessageAuthenticationCodeTests.java │ │ │ │ └── HmacAuthorizationHeaderTests.java │ │ │ ├── AbstractControllerTests.java │ │ │ ├── MockSearchComponent.java │ │ │ ├── ControllerTestsBase.java │ │ │ ├── MockReviewComponent.java │ │ │ └── workspace │ │ │ │ └── management │ │ │ │ └── CreateWorkspaceControllerTests.java │ │ │ ├── util │ │ │ └── HtmlUtilsTests.java │ │ │ ├── component │ │ │ └── workspace │ │ │ │ ├── WorkspaceVersionTests.java │ │ │ │ ├── WorkspaceBranchTests.java │ │ │ │ └── MockWorkspaceDao.java │ │ │ └── configuration │ │ │ ├── ElasticsearchConfigurerTests.java │ │ │ └── RedisConfigurerTests.java │ └── integrationTest │ │ └── java │ │ └── com │ │ └── structurizr │ │ └── onpremises │ │ └── component │ │ └── search │ │ ├── ApacheLuceneSearchComponentTests.java │ │ └── ElasticSearchComponentImplTests.java └── Dockerfile ├── docs ├── README.md └── docs │ └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── trivy.sh ├── README.md ├── LICENSE ├── ui.sh └── .gitignore /gradle.properties: -------------------------------------------------------------------------------- 1 | structurizrVersion=5.0.2 -------------------------------------------------------------------------------- /structurizr-onpremises-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.0.0 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'structurizr-onpremises' 2 | include 'structurizr-onpremises-plugin' -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/auto-save.jspf: -------------------------------------------------------------------------------- 1 | <%-- empty file --%> -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/auto-refresh.jspf: -------------------------------------------------------------------------------- 1 | <%-- empty file --%> -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Docs have moved to [https://docs.structurizr.com/onpremises](https://docs.structurizr.com/onpremises). -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/onpremises/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/jboss-web.xml: -------------------------------------------------------------------------------- 1 | 2 | / 3 | -------------------------------------------------------------------------------- /docs/docs/README.md: -------------------------------------------------------------------------------- 1 | See [https://docs.structurizr.com/onpremises](https://docs.structurizr.com/onpremises) for the on-premises installation documentation. -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/onpremises/HEAD/structurizr-onpremises/src/main/webapp/favicon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "structurizr-onpremises/src/main/webapp/static/themes"] 2 | path = structurizr-onpremises/src/main/webapp/static/themes 3 | url = https://github.com/structurizr/themes 4 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/AuthenticationMethod.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | public enum AuthenticationMethod { 4 | 5 | LOCAL, 6 | SAML 7 | 8 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/review/ReviewType.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain.review; 2 | 3 | public enum ReviewType { 4 | 5 | General, 6 | Risk, 7 | STRIDE 8 | 9 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/raw-views/json.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="application/json;charset=UTF-8" pageEncoding="UTF-8" %><%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %> -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/raw-views/plaintext.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/plain;charset=UTF-8" pageEncoding="UTF-8" %><%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %> -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | public enum MessageType { 4 | 5 | success, 6 | info, 7 | warning, 8 | danger 9 | 10 | } -------------------------------------------------------------------------------- /trivy.sh: -------------------------------------------------------------------------------- 1 | #docker run aquasec/trivy image structurizr/onpremises 2 | docker run -e "TRIVY_DB_REPOSITORY=public.ecr.aws/aquasecurity/trivy-db" -e "TRIVY_JAVA_DB_REPOSITORY=public.ecr.aws/aquasecurity/trivy-java-db" aquasec/trivy image structurizr/onpremises -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /structurizr-onpremises-plugin/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceEventListener.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | public interface WorkspaceEventListener { 4 | 5 | void beforeSave(WorkspaceEvent event); 6 | 7 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/feature-not-available.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | This feature is not avaiable on this installation. 6 |

7 |
8 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/public-dsl-editor-disabled.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | The DSL editor is not enabled on this installation. 6 |

7 |
8 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/home.jsp: -------------------------------------------------------------------------------- 1 | <%@ include file="/WEB-INF/fragments/quick-navigation.jspf" %> 2 | 3 |
4 |
5 | <%@ include file="/WEB-INF/fragments/dashboard-workspaces.jspf" %> 6 |
7 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/workspace-could-not-be-locked.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Workspace could not be locked

4 |

5 | The workspace could not be locked, please try again. 6 |

7 |
8 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/StructurizrEnvironmentVariables.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | public class StructurizrEnvironmentVariables { 4 | 5 | public static final String ENCRYPTION_PASSPHRASE = "STRUCTURIZR_ENCRYPTION"; 6 | 7 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/dashboard.jsp: -------------------------------------------------------------------------------- 1 | <%@ include file="/WEB-INF/fragments/quick-navigation.jspf" %> 2 | 3 |
4 |
5 | <%@ include file="/WEB-INF/fragments/dashboard-workspaces.jspf" %> 6 |
7 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/RandomGuidGenerator.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import java.util.UUID; 4 | 5 | public final class RandomGuidGenerator { 6 | 7 | public String generate() { 8 | return UUID.randomUUID().toString(); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/workspace-is-client-side-encrypted.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | This feature is not available when your workspace is client-side encrypted. 6 |

7 |
8 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/workspace-is-readonly.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | You don't have write access to this workspace, so the editor feature is unavailable. 6 |

7 |
8 |
-------------------------------------------------------------------------------- /structurizr-onpremises-plugin/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceEvent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | public interface WorkspaceEvent { 4 | 5 | WorkspaceProperties getWorkspaceProperties(); 6 | 7 | String getJson(); 8 | 9 | void setJson(String json); 10 | 11 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/glassfish-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | / 6 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceMetadataCache.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | interface WorkspaceMetadataCache { 4 | 5 | WorkspaceMetaData get(long workspaceId); 6 | 7 | void put(WorkspaceMetaData workspaceMetaData); 8 | 9 | void stop(); 10 | 11 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/DocumentType.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | class DocumentType { 4 | 5 | static final String WORKSPACE = "workspace"; 6 | static final String DIAGRAM = "diagram"; 7 | static final String DOCUMENTATION = "documentation"; 8 | static final String DECISION = "decision"; 9 | 10 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/dsl-editor-disabled.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | The DSL editor is not enabled on this installation 6 | - please use the Structurizr CLI instead. 7 |

8 |
9 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | 6 | public final class JsonUtils { 7 | 8 | public static String base64(String json) { 9 | return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/ReviewException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.review; 2 | 3 | public class ReviewException extends RuntimeException { 4 | 5 | ReviewException(String message) { 6 | super(message); 7 | } 8 | 9 | ReviewException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/ReviewComponentException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.review; 2 | 3 | public class ReviewComponentException extends Exception { 4 | 5 | ReviewComponentException(String message) { 6 | super(message); 7 | } 8 | 9 | ReviewComponentException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/SearchComponentException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | public class SearchComponentException extends Exception { 4 | 5 | public SearchComponentException(String message) { 6 | super(message); 7 | } 8 | 9 | public SearchComponentException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/review/CommentType.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain.review; 2 | 3 | public enum CommentType { 4 | 5 | General, 6 | 7 | RiskLow, 8 | RiskMedium, 9 | RiskHigh, 10 | 11 | STRIDE_Spoofing, 12 | STRIDE_Tampering, 13 | STRIDE_Repudiation, 14 | STRIDE_InformationDisclosure, 15 | STRIDE_DenialOfService, 16 | STRIDE_ElevationOfPrivilege 17 | 18 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/workspace-branches-not-enabled.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Feature not available

4 |

5 | Workspace branches are not enabled on this installation 6 | - see Workspace branches 7 | for details. 8 |

9 |
10 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED) 7 | public class ApiException extends RuntimeException { 8 | 9 | public ApiException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | 5 | public class Role implements GrantedAuthority { 6 | 7 | private final String name; 8 | 9 | public Role(String name) { 10 | this.name = name; 11 | } 12 | 13 | @Override 14 | public String getAuthority() { 15 | return name; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/user-profile.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

User profile

4 | 5 |

6 | Username: ${user.username} 7 |

8 | 9 |

10 | Roles: 11 |

12 | 13 |
    14 | 15 |
  • ${role}
  • 16 |
    17 |
18 |
19 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceComponentException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | public class WorkspaceComponentException extends RuntimeException { 4 | 5 | public WorkspaceComponentException(String message) { 6 | super(message); 7 | } 8 | 9 | public WorkspaceComponentException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/RawViewResolver.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import org.springframework.web.servlet.view.UrlBasedViewResolver; 4 | 5 | import java.util.Locale; 6 | 7 | public class RawViewResolver extends UrlBasedViewResolver { 8 | 9 | @Override 10 | protected boolean canHandle(String viewName, Locale locale) { 11 | return "json".equals(viewName) || "plaintext".equals(viewName); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | class HttpHeaders { 4 | 5 | public static final String AUTHORIZATION = "Authorization"; 6 | public static final String X_AUTHORIZATION = "X-Authorization"; 7 | public static final String CONTENT_TYPE = "Content-Type"; 8 | public static final String CONTENT_MD5 = "Content-MD5"; 9 | public static final String NONCE = "Nonce"; 10 | 11 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/EarlyAccessFeaturesNotAvailableException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | public final class EarlyAccessFeaturesNotAvailableException extends RuntimeException { 4 | 5 | public EarlyAccessFeaturesNotAvailableException(String feature) { 6 | super(feature + " is not available in this build - see https://docs.structurizr.com/onpremises for details of how to gain early access to new features"); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/UserType.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | public final class UserType { 4 | 5 | public UserType() { 6 | } 7 | 8 | public boolean isAllowedToShareWorkspacesWithLink() { 9 | return true; 10 | } 11 | 12 | public boolean isAllowedToUseImageEmbed() { 13 | return false; 14 | } 15 | 16 | public boolean isAllowedToLockWorkspaces() { 17 | return true; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Structurizr on-premises installation 2 | 3 | The Structurizr on-premises installation is a standalone version of Structurizr that can be run locally on your own 4 | infrastructure. It’s a Jakarta EE/Spring 6 web application, packaged as a .war file, for deployment into any 5 | compatible Jakarta EE server, such as Apache Tomcat 10. For ease of deployment, by default, 6 | all data is stored on the local file system. 7 | 8 | See [https://docs.structurizr.com/onpremises](https://docs.structurizr.com/onpremises) for documentation. -------------------------------------------------------------------------------- /structurizr-onpremises/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tomcat:10.1.41-jre21-temurin-noble 2 | ENV PORT=8080 3 | 4 | RUN set -eux; \ 5 | apt-get update; \ 6 | apt-get install -y --no-install-recommends graphviz 7 | 8 | RUN sed -i 's/port="8080"/port="${http.port}" maxPostSize="10485760"/' conf/server.xml \ 9 | && echo 'export CATALINA_OPTS="-Xms512M -Xmx512M -Dhttp.port=$PORT"' > bin/setenv.sh 10 | 11 | ADD build/libs/structurizr-onpremises.war /usr/local/tomcat/webapps/ROOT.war 12 | 13 | EXPOSE ${PORT} 14 | 15 | CMD ["catalina.sh", "run"] -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/component.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/component.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/component.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/FileTypeAndContent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.review; 2 | 3 | class FileTypeAndContent { 4 | 5 | private final String extension; 6 | private final byte[] content; 7 | 8 | FileTypeAndContent(String extension, byte[] content) { 9 | this.extension = extension; 10 | this.content = content; 11 | } 12 | 13 | String getExtension() { 14 | return this.extension; 15 | } 16 | 17 | byte[] getContent() { 18 | return content; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /structurizr-onpremises-plugin/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceProperties.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.configuration.User; 4 | import com.structurizr.configuration.Visibility; 5 | 6 | import java.util.Date; 7 | import java.util.Set; 8 | 9 | public interface WorkspaceProperties { 10 | 11 | long getId(); 12 | 13 | String getName(); 14 | 15 | String getDescription(); 16 | 17 | Date getLastModifiedDate(); 18 | 19 | Visibility getVisibility(); 20 | 21 | Set getUsers(); 22 | 23 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/HmacContent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | class HmacContent { 4 | 5 | private final String[] strings; 6 | 7 | public HmacContent(String... strings) { 8 | this.strings = strings; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | StringBuilder buf = new StringBuilder(); 14 | for (String string : strings) { 15 | buf.append(string); 16 | buf.append("\n"); 17 | } 18 | 19 | return buf.toString(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/Configurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | abstract class Configurer { 6 | 7 | protected Properties properties; 8 | 9 | Configurer(Properties properties) { 10 | this.properties = properties; 11 | } 12 | 13 | void setDefault(String name, String defaultValue) { 14 | if (!properties.containsKey(name)) { 15 | properties.setProperty(name, defaultValue); 16 | } 17 | } 18 | 19 | abstract void apply(); 20 | 21 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/NoOpWorkspaceMetadataCache.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | /** 4 | * Workspace metadata cache implementation that does nothing. 5 | */ 6 | class NoOpWorkspaceMetadataCache implements WorkspaceMetadataCache { 7 | 8 | @Override 9 | public WorkspaceMetaData get(long workspaceId) { 10 | return null; 11 | } 12 | 13 | @Override 14 | public void put(WorkspaceMetaData workspaceMetaData) { 15 | } 16 | 17 | @Override 18 | public void stop() { 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/HttpUnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED) 7 | public class HttpUnauthorizedException extends RuntimeException { 8 | 9 | public HttpUnauthorizedException(String message) { 10 | super(message); 11 | } 12 | 13 | public HttpUnauthorizedException(Throwable cause) { 14 | this(cause.getMessage()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/WorkspacesApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class WorkspacesApiResponse { 7 | 8 | private final List workspaces = new ArrayList<>(); 9 | 10 | WorkspacesApiResponse() { 11 | } 12 | 13 | void add(WorkspaceApiResponse war) { 14 | this.workspaces.add(war); 15 | } 16 | 17 | public List getWorkspaces() { 18 | return new ArrayList<>(workspaces); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/api/HmacContentTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | public class HmacContentTests { 8 | 9 | 10 | @Test 11 | public void toString_WhenThereAreNoStrings() { 12 | assertEquals("", new HmacContent().toString()); 13 | } 14 | 15 | @Test 16 | public void toString_WhenThereAreSomeStrings() { 17 | assertEquals("String1\nString2\nString3\n", new HmacContent("String1", "String2", "String3").toString()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/workspace-locked.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Workspace locked

4 |

5 | This workspace was locked by ${workspace.lockedUser} at . 6 |

7 | 8 |

9 | Workspace summary 10 | | 11 | Diagram viewer 12 |

13 |
14 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/AzureBlobStorageConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | import static com.structurizr.onpremises.configuration.StructurizrProperties.*; 6 | 7 | public class AzureBlobStorageConfigurer extends Configurer { 8 | 9 | public AzureBlobStorageConfigurer(Properties properties) { 10 | super(properties); 11 | } 12 | 13 | public void apply() { 14 | setDefault(AZURE_BLOB_STORAGE_ACCOUNT_NAME, ""); 15 | setDefault(AZURE_BLOB_STORAGE_ACCESS_KEY, ""); 16 | setDefault(AZURE_BLOB_STORAGE_CONTAINER_NAME, ""); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-session-local.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/InputStreamAndContentLength.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | import java.io.InputStream; 4 | 5 | public final class InputStreamAndContentLength { 6 | 7 | private final InputStream inputStream; 8 | private final long contentLength; 9 | 10 | public InputStreamAndContentLength(InputStream inputStream, long contentLength) { 11 | this.inputStream = inputStream; 12 | this.contentLength = contentLength; 13 | } 14 | 15 | public InputStream getInputStream() { 16 | return inputStream; 17 | } 18 | 19 | public long getContentLength() { 20 | return contentLength; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/SearchComponent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | import com.structurizr.Workspace; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | /** 9 | * Provides search facilities for workspaces. 10 | */ 11 | public interface SearchComponent { 12 | 13 | void start(); 14 | 15 | void stop(); 16 | 17 | boolean isEnabled(); 18 | 19 | void index(Workspace workspace) throws SearchComponentException; 20 | 21 | List search(String query, String type, Set workspaceIds) throws SearchComponentException; 22 | 23 | void delete(long workspaceId) throws SearchComponentException; 24 | 25 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/NoOpSpringSessionRepositoryFilter.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import jakarta.servlet.*; 4 | 5 | import java.io.IOException; 6 | 7 | public class NoOpSpringSessionRepositoryFilter implements Filter { 8 | 9 | @Override 10 | public void init(FilterConfig filterConfig) throws ServletException { 11 | } 12 | 13 | @Override 14 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 15 | filterChain.doFilter(servletRequest, servletResponse); 16 | } 17 | 18 | @Override 19 | public void destroy() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/error/Http404Controller.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.error; 2 | 3 | import com.structurizr.onpremises.web.AbstractController; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.ModelMap; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | 9 | @Controller 10 | public class Http404Controller extends AbstractController { 11 | 12 | @RequestMapping(value = "/404", method = RequestMethod.GET) 13 | public String show404Page(ModelMap model) { 14 | addCommonAttributes(model, "404", true); 15 | 16 | return "404"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/api/Md5DigestTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | public class Md5DigestTests { 8 | 9 | private final Md5Digest md5 = new Md5Digest(); 10 | 11 | @Test 12 | public void generate_TreatsNullAsEmptyContent() throws Exception { 13 | assertEquals(md5.generate(null), md5.generate("")); 14 | } 15 | 16 | @Test 17 | public void generate() throws Exception { 18 | assertEquals("ed076287532e86365e841e92bfc50d8c", md5.generate("Hello World!")); 19 | assertEquals("d41d8cd98f00b204e9800998ecf8427e", md5.generate("")); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/Md5Digest.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import javax.xml.bind.DatatypeConverter; 4 | import java.nio.charset.StandardCharsets; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | class Md5Digest { 9 | 10 | private static final String ALGORITHM = "MD5"; 11 | 12 | String generate(String content) throws NoSuchAlgorithmException { 13 | if (content == null) { 14 | content = ""; 15 | } 16 | 17 | MessageDigest digest = MessageDigest.getInstance(ALGORITHM); 18 | return DatatypeConverter.printHexBinary(digest.digest(content.getBytes(StandardCharsets.UTF_8))).toLowerCase(); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/api/HashBasedMessageAuthenticationCodeTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | public class HashBasedMessageAuthenticationCodeTests { 8 | 9 | @Test 10 | public void generate() throws Exception { 11 | // this example is taken from http://en.wikipedia.org/wiki/Hash-based_message_authentication_code 12 | HashBasedMessageAuthenticationCode code = new HashBasedMessageAuthenticationCode("key"); 13 | assertEquals("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", code.generate("The quick brown fox jumps over the lazy dog")); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/AmazonWebServicesS3Configurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | import static com.structurizr.onpremises.configuration.StructurizrProperties.*; 6 | 7 | class AmazonWebServicesS3Configurer extends Configurer { 8 | 9 | AmazonWebServicesS3Configurer(Properties properties) { 10 | super(properties); 11 | } 12 | 13 | void apply() { 14 | setDefault(AWS_S3_ACCESS_KEY_ID, ""); 15 | setDefault(AWS_S3_SECRET_ACCESS_KEY, ""); 16 | setDefault(AWS_S3_REGION, ""); 17 | setDefault(AWS_S3_BUCKET_NAME, ""); 18 | setDefault(AWS_S3_ENDPOINT, ""); 19 | setDefault(AWS_S3_PATH_STYLE_ACCESS, "false"); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/security/CsrfSecurityRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.security; 2 | 3 | import org.springframework.security.web.util.matcher.RequestMatcher; 4 | 5 | import jakarta.servlet.http.HttpServletRequest; 6 | 7 | public class CsrfSecurityRequestMatcher implements RequestMatcher { 8 | 9 | @Override 10 | public boolean matches(HttpServletRequest request) { 11 | String method = request.getMethod(); 12 | 13 | if ("POST".equals(method)) { 14 | String uri = request.getRequestURI(); 15 | 16 | if ( 17 | uri.startsWith("/login") 18 | ) { 19 | return true; 20 | } 21 | } 22 | 23 | return false; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-security.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/HtmlUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | public final class HtmlUtils { 4 | 5 | public static String filterHtml(String s) { 6 | if (s == null) { 7 | return null; 8 | } 9 | 10 | s = s.replaceAll("<", ""); 11 | s = s.replaceAll(">", ""); 12 | s = s.replaceAll(" ", ""); 13 | s = s.replaceAll("(?s)", ""); 14 | s = s.replaceAll("(?s)<[a-zA-Z]{1,10}.*?>", ""); 15 | s = s.replaceAll("(?s)", ""); 16 | 17 | return s; 18 | } 19 | 20 | public static String escapeQuoteCharacters(String s) { 21 | if (s == null) { 22 | return null; 23 | } 24 | 25 | s = s.replace("'", "\\'"); 26 | 27 | return s; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/error/Http500Controller.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.error; 2 | 3 | import com.structurizr.onpremises.web.AbstractController; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | 11 | @Controller 12 | public class Http500Controller extends AbstractController { 13 | 14 | private static Log log = LogFactory.getLog(Http500Controller.class); 15 | 16 | @RequestMapping(value = "/500", method = RequestMethod.GET) 17 | public String showErrorPage(ModelMap model) { 18 | addCommonAttributes(model, "500", true); 19 | 20 | return "500"; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-security-saml.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceLockResponse.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | public class WorkspaceLockResponse { 4 | 5 | private final boolean success; 6 | private final boolean locked; 7 | private String message; 8 | 9 | public WorkspaceLockResponse(boolean success, boolean locked) { 10 | this.success = success; 11 | this.locked = locked; 12 | } 13 | 14 | public WorkspaceLockResponse(boolean success, boolean locked, String message) { 15 | this.success = success; 16 | this.locked = locked; 17 | this.message = message; 18 | } 19 | 20 | public boolean isSuccess() { 21 | return success; 22 | } 23 | 24 | public boolean isLocked() { 25 | return locked; 26 | } 27 | 28 | public String getMessage() { 29 | return message; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/PropertyPlaceholderConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import java.io.File; 4 | import java.util.Properties; 5 | 6 | public final class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer { 7 | 8 | private static final String STRUCTURIZR_DATA_DIRECTORY_PROPERTY_NAME = "structurizr.dataDirectory"; 9 | 10 | public PropertyPlaceholderConfigurer() { 11 | Properties properties = Configuration.getInstance().getProperties(); 12 | File dataDirectory = Configuration.getInstance().getDataDirectory(); 13 | properties.setProperty(STRUCTURIZR_DATA_DIRECTORY_PROPERTY_NAME, dataDirectory.getAbsolutePath()); 14 | 15 | setLocalOverride(true); 16 | setProperties(properties); 17 | setIgnoreUnresolvablePlaceholders(false); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | public class ApiResponse { 4 | 5 | private boolean success = false; 6 | private String message = ""; 7 | 8 | public ApiResponse(String message) { 9 | this(true, message); 10 | } 11 | 12 | public ApiResponse(boolean success, String message) { 13 | this.success = success; 14 | this.message = message; 15 | } 16 | 17 | ApiResponse(Exception e) { 18 | this(false, e.getMessage()); 19 | } 20 | 21 | public boolean isSuccess() { 22 | return success; 23 | } 24 | 25 | void setSuccess(boolean success) { 26 | this.success = success; 27 | } 28 | 29 | public String getMessage() { 30 | return message; 31 | } 32 | 33 | void setMessage(String message) { 34 | this.message = message; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/Image.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | import java.util.Date; 4 | 5 | public class Image { 6 | 7 | private final String name; 8 | private final long size; 9 | private final Date lastModifiedDate; 10 | private String url; 11 | 12 | public Image(String name, long size, Date lastModifiedDate) { 13 | this.name = name; 14 | this.size = size; 15 | this.lastModifiedDate = lastModifiedDate; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public long getSizeInKB() { 23 | return size / 1024; 24 | } 25 | 26 | public Date getLastModifiedDate() { 27 | return lastModifiedDate; 28 | } 29 | 30 | public String getUrl() { 31 | return url; 32 | } 33 | 34 | public void setUrl(String url) { 35 | this.url = url; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/user/UserProfileController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.user; 2 | 3 | import com.structurizr.onpremises.web.AbstractController; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.ModelMap; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | 10 | @Controller 11 | public class UserProfileController extends AbstractController { 12 | 13 | private static final String VIEW = "user-profile"; 14 | 15 | @RequestMapping(value = "/user/profile", method = RequestMethod.GET) 16 | @PreAuthorize("isAuthenticated()") 17 | public String showUserProfilePage(ModelMap model) { 18 | model.addAttribute("user", getUser()); 19 | 20 | addCommonAttributes(model, "User Profile", true); 21 | 22 | return VIEW; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/AbstractControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import com.structurizr.onpremises.web.home.HomePageController; 4 | 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | public class AbstractControllerTests extends ControllerTestsBase { 12 | 13 | private AbstractController controller; 14 | 15 | @BeforeEach 16 | public void setUp() { 17 | controller = new HomePageController(); // any controller will do 18 | } 19 | 20 | @Test 21 | public void isAuthenticated() { 22 | clearUser(); 23 | assertFalse(controller.isAuthenticated()); 24 | 25 | setUser("username"); 26 | assertTrue(controller.isAuthenticated()); 27 | 28 | clearUser(); 29 | assertFalse(controller.isAuthenticated()); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/NoOpSearchComponentImpl.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | import com.structurizr.Workspace; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | /** 10 | * A search component implementation that does nothing. 11 | */ 12 | class NoOpSearchComponentImpl implements SearchComponent { 13 | 14 | NoOpSearchComponentImpl() { 15 | } 16 | 17 | @Override 18 | public void start() { 19 | } 20 | 21 | @Override 22 | public void stop() { 23 | } 24 | 25 | @Override 26 | public boolean isEnabled() { 27 | return false; 28 | } 29 | 30 | @Override 31 | public void index(Workspace workspace) { 32 | } 33 | 34 | @Override 35 | public List search(String query, String type, Set workspaceIds) { 36 | return new ArrayList<>(); 37 | } 38 | 39 | @Override 40 | public void delete(long workspaceId) { 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/MockSearchComponent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.onpremises.component.search.SearchComponent; 5 | import com.structurizr.onpremises.component.search.SearchComponentException; 6 | import com.structurizr.onpremises.component.search.SearchResult; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class MockSearchComponent implements SearchComponent { 12 | 13 | @Override 14 | public void start() { 15 | } 16 | 17 | @Override 18 | public void stop() { 19 | } 20 | 21 | @Override 22 | public boolean isEnabled() { 23 | return true; 24 | } 25 | 26 | @Override 27 | public void index(Workspace workspace) { 28 | } 29 | 30 | @Override 31 | public List search(String query, String type, Set workspaceIds) { 32 | return null; 33 | } 34 | 35 | @Override 36 | public void delete(long workspaceId) { 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/HashBasedMessageAuthenticationCode.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import javax.crypto.Mac; 4 | import javax.crypto.spec.SecretKeySpec; 5 | import javax.xml.bind.DatatypeConverter; 6 | import java.security.InvalidKeyException; 7 | import java.security.NoSuchAlgorithmException; 8 | 9 | class HashBasedMessageAuthenticationCode { 10 | 11 | private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; 12 | 13 | private String apiSecret; 14 | 15 | HashBasedMessageAuthenticationCode(String apiSecret) { 16 | this.apiSecret = apiSecret; 17 | } 18 | 19 | String generate(String content) throws NoSuchAlgorithmException, InvalidKeyException { 20 | SecretKeySpec signingKey = new SecretKeySpec(apiSecret.getBytes(), HMAC_SHA256_ALGORITHM); 21 | Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); 22 | mac.init(signingKey); 23 | byte[] rawHmac = mac.doFinal(content.getBytes()); 24 | return DatatypeConverter.printHexBinary(rawHmac).toLowerCase(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/Messages.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Stack; 7 | 8 | public final class Messages implements Serializable { 9 | 10 | private Stack messages = new Stack<>(); 11 | 12 | public void addSuccessMessage(String message) { 13 | this.messages.push(new Message(MessageType.success, message)); 14 | } 15 | 16 | public void addWarningMessage(String message) { 17 | this.messages.push(new Message(MessageType.warning, message)); 18 | } 19 | 20 | public void addErrorMessage(String message) { 21 | this.messages.push(new Message(MessageType.danger, message)); 22 | } 23 | 24 | public List getUnreadMessages() { 25 | List unreadMessages = new LinkedList<>(); 26 | while (!this.messages.isEmpty()) { 27 | unreadMessages.add(this.messages.pop()); 28 | } 29 | 30 | return unreadMessages; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/review/Diagram.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain.review; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Diagram { 7 | 8 | private int id; 9 | private String url; 10 | 11 | private List comments = new ArrayList<>(); 12 | 13 | Diagram() { 14 | } 15 | 16 | public Diagram(int id, String url) { 17 | this.id = id; 18 | this.url = url; 19 | } 20 | 21 | public int getId() { 22 | return id; 23 | } 24 | 25 | void setId(int id) { 26 | this.id = id; 27 | } 28 | 29 | public String getUrl() { 30 | return url; 31 | } 32 | 33 | void setUrl(String url) { 34 | this.url = url; 35 | } 36 | 37 | public List getComments() { 38 | return comments; 39 | } 40 | 41 | public void setComments(List comments) { 42 | this.comments = comments; 43 | } 44 | 45 | void addComment(Comment comment) { 46 | this.comments.add(comment); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-session-redis.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/security/BcryptController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class BcryptController { 12 | 13 | private final BCryptPasswordEncoder bCryptPasswordEncoder; 14 | 15 | @Autowired 16 | public BcryptController(BCryptPasswordEncoder bCryptPasswordEncoder) { 17 | this.bCryptPasswordEncoder = bCryptPasswordEncoder; 18 | } 19 | 20 | @RequestMapping(value = "/bcrypt/{plaintext}", method = RequestMethod.GET, produces = "text/plain") 21 | public String getWorkspace(@PathVariable("plaintext") String plaintext) { 22 | return bCryptPasswordEncoder.encode(plaintext) + "\n"; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Structurizr Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/integrationTest/java/com/structurizr/onpremises/component/search/ApacheLuceneSearchComponentTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | 4 | 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.springframework.util.FileSystemUtils; 8 | 9 | import java.io.File; 10 | 11 | public class ApacheLuceneSearchComponentTests extends AbstractSearchComponentTests { 12 | 13 | private static final File DATA_DIRECTORY = new File("./build/ApacheLuceneSearchComponentTests"); 14 | 15 | private ApacheLuceneSearchComponentImpl searchComponent; 16 | 17 | @BeforeEach 18 | public void setUp() { 19 | DATA_DIRECTORY.mkdirs(); 20 | searchComponent = new ApacheLuceneSearchComponentImpl(DATA_DIRECTORY); 21 | searchComponent.start(); 22 | } 23 | 24 | @AfterEach 25 | public void tearDown() { 26 | searchComponent.stop(); 27 | FileSystemUtils.deleteRecursively(DATA_DIRECTORY); 28 | } 29 | 30 | @Override 31 | protected SearchComponent getSearchComponent() { 32 | return searchComponent; 33 | } 34 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/Message.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | public final class Message implements Serializable { 6 | 7 | private MessageType type; 8 | private String text; 9 | 10 | public Message(MessageType type, String text) { 11 | this.type = type; 12 | this.text = text; 13 | } 14 | 15 | public MessageType getType() { 16 | return type; 17 | } 18 | 19 | public String getText() { 20 | return text; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | 28 | Message message = (Message) o; 29 | 30 | if (!text.equals(message.text)) return false; 31 | if (type != message.type) return false; 32 | 33 | return true; 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | int result = type.hashCode(); 39 | result = 31 * result + text.hashCode(); 40 | return result; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | appender.console.type = Console 2 | appender.console.name = LogToConsole 3 | appender.console.layout.type = PatternLayout 4 | appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n 5 | 6 | appender.file.type = File 7 | appender.file.name = LogToFile 8 | appender.file.fileName=${sys:structurizr.dataDirectory}/logs/structurizr.log 9 | appender.file.layout.type=PatternLayout 10 | appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n 11 | 12 | logger.app.name = com.structurizr 13 | logger.app.level = info 14 | logger.app.additivity = false 15 | logger.app.appenderRef.console.ref = LogToConsole 16 | logger.app.appenderRef.file.ref = LogToFile 17 | 18 | logger.springSecurity.name = org.springframework.security 19 | logger.springSecurity.level = warn 20 | logger.springSecurity.additivity = false 21 | logger.springSecurity.appenderRef.console.ref = LogToConsole 22 | logger.springSecurity.appenderRef.file.ref = LogToFile 23 | 24 | rootLogger.level = warn 25 | rootLogger.appenderRef.stdout.ref = LogToConsole 26 | rootLogger.appenderRef.file.ref = LogToFile -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/SearchResponse.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | class SearchResponse { 8 | 9 | private SearchHits hits; 10 | 11 | SearchResponse() { 12 | } 13 | 14 | public SearchHits getHits() { 15 | return hits; 16 | } 17 | 18 | void setHits(SearchHits hits) { 19 | this.hits = hits; 20 | } 21 | 22 | } 23 | 24 | class SearchHits { 25 | 26 | private List hits; 27 | 28 | public SearchHits() { 29 | } 30 | 31 | public List getHits() { 32 | return hits; 33 | } 34 | 35 | void setHits(List hits) { 36 | this.hits = hits; 37 | } 38 | 39 | } 40 | 41 | class SearchHit { 42 | 43 | @JsonProperty("_source") 44 | private Document source; 45 | 46 | SearchHit() { 47 | } 48 | 49 | public Document getSource() { 50 | return source; 51 | } 52 | 53 | public void setSource(Document source) { 54 | this.source = source; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/ReviewComponent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.review; 2 | 3 | import com.structurizr.onpremises.domain.User; 4 | import com.structurizr.onpremises.domain.review.Review; 5 | import com.structurizr.onpremises.domain.review.ReviewType; 6 | import com.structurizr.onpremises.domain.review.Session; 7 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 8 | 9 | import java.util.Collection; 10 | 11 | /** 12 | * Provides access to and manages reviews. 13 | */ 14 | public interface ReviewComponent { 15 | 16 | public static final String FILE = "file"; 17 | public static final String AMAZON_WEB_SERVICES_S3 = "aws-s3"; 18 | 19 | Review createReview(User user, Long workspaceId, String[] files, ReviewType type); 20 | 21 | Collection getReviews(); 22 | 23 | Review getReview(String reviewId); 24 | 25 | void submitReview(String reviewId, Session reviewSession); 26 | 27 | InputStreamAndContentLength getDiagram(String reviewId, String filename); 28 | 29 | void lockReview(String reviewId); 30 | 31 | void unlockReview(String reviewId); 32 | 33 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/review/ReviewDao.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.review; 2 | 3 | import com.structurizr.onpremises.domain.review.Review; 4 | import com.structurizr.onpremises.domain.review.Session; 5 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 6 | 7 | import java.util.Collection; 8 | import java.util.Set; 9 | 10 | interface ReviewDao { 11 | 12 | Set getReviewIds() throws ReviewComponentException; 13 | 14 | void putReview(Review review) throws ReviewComponentException; 15 | 16 | Review getReview(String reviewId) throws ReviewComponentException; 17 | 18 | void submitReview(String reviewId, Session reviewSession) throws ReviewComponentException; 19 | 20 | Collection getReviewSessions(String reviewId) throws ReviewComponentException; 21 | 22 | void putDiagram(String reviewId, String filename, byte[] bytes) throws ReviewComponentException; 23 | 24 | boolean reviewExists(String reviewId) throws ReviewComponentException; 25 | 26 | InputStreamAndContentLength getDiagram(String reviewId, String filename) throws ReviewComponentException; 27 | 28 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/util/HtmlUtilsTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | 8 | public class HtmlUtilsTests { 9 | 10 | @Test 11 | public void test_filterHTML() { 12 | assertEquals("Here is some text.", HtmlUtils.filterHtml("Here is some text.")); 13 | assertEquals("Here is some text.", HtmlUtils.filterHtml("Here is some text.")); 14 | assertEquals("Here is some text.", HtmlUtils.filterHtml("Here is some text.")); 15 | assertEquals("Here is a link.", HtmlUtils.filterHtml("Here is a link.")); 16 | assertEquals("Here is a link.", HtmlUtils.filterHtml("Here is a link.")); 17 | assertEquals("Here is some text", HtmlUtils.filterHtml("Here is <some> text")); 18 | assertEquals("alert('hello')", HtmlUtils.filterHtml("")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/api/HmacAuthorizationHeader.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.api; 2 | 3 | import java.util.Base64; 4 | 5 | class HmacAuthorizationHeader { 6 | 7 | private final String apiKey; 8 | private final String hmac; 9 | 10 | HmacAuthorizationHeader(String apiKey, String hmac) { 11 | this.apiKey = apiKey; 12 | this.hmac = hmac; 13 | } 14 | 15 | public String getApiKey() { 16 | return apiKey; 17 | } 18 | 19 | public String getHmac() { 20 | return hmac; 21 | } 22 | 23 | public String format() { 24 | return apiKey + ":" + Base64.getEncoder().encodeToString(hmac.getBytes()); 25 | } 26 | 27 | static HmacAuthorizationHeader parse(String s) { 28 | String[] parts = s.split(":"); 29 | if (parts.length == 2) { 30 | String apiKey = parts[0]; 31 | String hmac = new String(Base64.getDecoder().decode(parts[1])); 32 | 33 | return new HmacAuthorizationHeader(apiKey, hmac); 34 | } else { 35 | throw new IllegalArgumentException("Invalid authorization header"); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/Features.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | public class Features { 4 | 5 | public static final String UI_DSL_EDITOR = "structurizr.feature.ui.dsleditor"; 6 | public static final String UI_WORKSPACE_USERS = "structurizr.feature.ui.workspaceusers"; 7 | public static final String UI_WORKSPACE_SETTINGS = "structurizr.feature.ui.workspacesettings"; 8 | 9 | public static final String WORKSPACE_SEARCH = "structurizr.feature.workspace.search"; 10 | public static final String WORKSPACE_ARCHIVING = "structurizr.feature.workspace.archiving"; 11 | public static final String WORKSPACE_BRANCHES = "structurizr.feature.workspace.branches"; 12 | public static final String WORKSPACE_SCOPE_VALIDATION = "structurizr.feature.workspace.scope"; 13 | public static final String WORKSPACE_SCOPE_VALIDATION_STRICT = "strict"; 14 | public static final String WORKSPACE_SCOPE_VALIDATION_RELAXED = "relaxed"; 15 | 16 | public static final String DIAGRAM_REVIEWS = "structurizr.feature.diagramreviews"; 17 | 18 | public static final String DIAGRAM_ANONYMOUS_THUMBNAILS = "structurizr.feature.diagram.anonymousthumbnails"; 19 | 20 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/dsl/DslEditorResponse.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.dsl; 2 | 3 | public final class DslEditorResponse { 4 | 5 | private final boolean success; 6 | private final String message; 7 | private final int lineNumber; 8 | private final String workspace; 9 | 10 | DslEditorResponse(boolean success, String message) { 11 | this(success, message, 0); 12 | } 13 | 14 | DslEditorResponse(boolean success, String message, int lineNumber) { 15 | this.success = success; 16 | this.message = message; 17 | this.lineNumber = lineNumber; 18 | this.workspace = null; 19 | } 20 | 21 | DslEditorResponse(String workspace) { 22 | this.success = true; 23 | this.workspace = workspace; 24 | this.message = null; 25 | this.lineNumber = 1; 26 | } 27 | 28 | public boolean isSuccess() { 29 | return success; 30 | } 31 | 32 | public String getMessage() { 33 | return message; 34 | } 35 | 36 | public int getLineNumber() { 37 | return lineNumber; 38 | } 39 | 40 | public String getWorkspace() { 41 | return workspace; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/SamlConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | class SamlConfigurer extends Configurer { 6 | 7 | SamlConfigurer(Properties properties) { 8 | super(properties); 9 | } 10 | 11 | private static final String DEFAULT_REGISTRATION_ID = "structurizr"; 12 | 13 | private static final String DEFAULT_SAML_ATTRIBUTE_USERNAME = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; 14 | private static final String DEFAULT_SAML_ATTRIBUTE_ROLE = "http://schemas.xmlsoap.org/claims/Group"; 15 | 16 | void apply() { 17 | setDefault(StructurizrProperties.SAML_REGISTRATION_ID, DEFAULT_REGISTRATION_ID); 18 | setDefault(StructurizrProperties.SAML_ENTITY_ID, ""); 19 | setDefault(StructurizrProperties.SAML_METADATA, ""); 20 | setDefault(StructurizrProperties.SAML_SIGNING_CERTIFICATE, ""); 21 | setDefault(StructurizrProperties.SAML_SIGNING_PRIVATE_KEY, ""); 22 | setDefault(StructurizrProperties.SAML_ATTRIBUTE_USERNAME, DEFAULT_SAML_ATTRIBUTE_USERNAME); 23 | setDefault(StructurizrProperties.SAML_ATTRIBUTE_ROLE, DEFAULT_SAML_ATTRIBUTE_ROLE); 24 | } 25 | 26 | 27 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/ControllerTestsBase.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import com.structurizr.onpremises.domain.Role; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class ControllerTestsBase { 13 | 14 | protected void clearUser() { 15 | SecurityContextHolder.getContext().setAuthentication(null); 16 | } 17 | 18 | protected void setUser(String username, String... roleNames) { 19 | Set roles = new HashSet<>(); 20 | for (String roleName : roleNames) { 21 | roles.add(new Role(roleName)); 22 | } 23 | 24 | UserDetails userDetails = new org.springframework.security.core.userdetails.User(username, "password", roles); 25 | Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, roles); 26 | SecurityContextHolder.getContext().setAuthentication(authentication); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceBranch.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.util.StringUtils; 4 | 5 | public final class WorkspaceBranch { 6 | 7 | public static final String NO_BRANCH = ""; 8 | public static final String MAIN_BRANCH = "main"; 9 | 10 | private static final String BRANCH_NAME_REGEX = "[a-zA-Z0-9][a-zA-Z0-9-_.]*"; 11 | private static final int MAX_LENGTH = 100; 12 | 13 | private final String name; 14 | 15 | public WorkspaceBranch(String name) { 16 | this.name = name; 17 | } 18 | 19 | public static void validateBranchName(String name) { 20 | if (!StringUtils.isNullOrEmpty(name) && !isValidBranchName(name)) { 21 | throw new IllegalArgumentException("The branch name \"" + name + "\" is invalid"); 22 | } 23 | } 24 | 25 | public static boolean isMainBranch(String branch) { 26 | return StringUtils.isNullOrEmpty(branch) || branch.equalsIgnoreCase(MAIN_BRANCH); 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | public static boolean isValidBranchName(String name) { 34 | return name.matches(BRANCH_NAME_REGEX) && name.length() < MAX_LENGTH; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/WorkspaceValidationUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.onpremises.configuration.Configuration; 5 | import com.structurizr.onpremises.configuration.Features; 6 | import com.structurizr.validation.WorkspaceScopeValidationException; 7 | import com.structurizr.validation.WorkspaceScopeValidatorFactory; 8 | 9 | public class WorkspaceValidationUtils { 10 | 11 | public static void validateWorkspaceScope(Workspace workspace) throws WorkspaceScopeValidationException { 12 | // if workspace scope validation is enabled, reject workspaces without a defined scope 13 | if (Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_SCOPE_VALIDATION)) { 14 | if (workspace.getConfiguration().getScope() == null) { 15 | throw new WorkspaceScopeValidationException("Strict workspace scope validation has been enabled for this on-premises installation. Unscoped workspaces are not permitted - see https://docs.structurizr.com/workspaces for more information."); 16 | } 17 | } 18 | 19 | // validate workspace scope 20 | WorkspaceScopeValidatorFactory.getValidator(workspace).validate(workspace); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/StructurizrDataDirectory.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import com.structurizr.util.StringUtils; 4 | 5 | public final class StructurizrDataDirectory { 6 | 7 | public static final String DEFAULT_DATA_DIRECTORY_PATH = "/usr/local/structurizr"; 8 | 9 | private static final String DATA_DIRECTORY_SYSTEM_PROPERTY = "structurizr.dataDirectory"; 10 | private static final String DATA_DIRECTORY_ENVIRONMENT_VARIABLE = "STRUCTURIZR_DATA_DIRECTORY"; 11 | 12 | public static String getLocation() { 13 | String value = getSystemPropertyIgnoreCase(); 14 | if (!StringUtils.isNullOrEmpty(value)) { 15 | return value; 16 | } 17 | 18 | value = System.getenv(DATA_DIRECTORY_ENVIRONMENT_VARIABLE); 19 | if (!StringUtils.isNullOrEmpty(value)) { 20 | return value; 21 | } 22 | 23 | return DEFAULT_DATA_DIRECTORY_PATH; 24 | } 25 | 26 | private static String getSystemPropertyIgnoreCase() { 27 | for (String key : System.getProperties().stringPropertyNames()) { 28 | if (key.equalsIgnoreCase(DATA_DIRECTORY_SYSTEM_PROPERTY)) { 29 | return System.getProperty(key); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/AmazonS3ClientUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 4 | import com.amazonaws.auth.BasicAWSCredentials; 5 | import com.amazonaws.services.s3.AmazonS3; 6 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 7 | import com.structurizr.util.StringUtils; 8 | 9 | public class AmazonS3ClientUtils { 10 | 11 | public static AmazonS3 create(String accessKeyId, String secretAccessKey, String region, String endpoint, boolean pathStyleAccessEnabled) { 12 | AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard(); 13 | 14 | if (!StringUtils.isNullOrEmpty(endpoint)) { 15 | builder.withEndpointConfiguration(new AmazonS3ClientBuilder.EndpointConfiguration(endpoint, region)); 16 | } else if (!StringUtils.isNullOrEmpty(region)) { 17 | builder.withRegion(region); 18 | } 19 | 20 | builder.withPathStyleAccessEnabled(pathStyleAccessEnabled); 21 | 22 | if (!StringUtils.isNullOrEmpty(accessKeyId) && !StringUtils.isNullOrEmpty(secretAccessKey)) { 23 | builder.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretAccessKey))); 24 | } 25 | 26 | return builder.build(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/security/AuthenticationFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.security; 2 | 3 | import com.structurizr.util.StringUtils; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.springframework.security.authentication.BadCredentialsException; 7 | import org.springframework.security.core.AuthenticationException; 8 | 9 | import jakarta.servlet.ServletException; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import jakarta.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | public class AuthenticationFailureHandler implements org.springframework.security.web.authentication.AuthenticationFailureHandler { 15 | 16 | private static Log log = LogFactory.getLog(AuthenticationFailureHandler.class); 17 | 18 | @Override 19 | public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { 20 | String username = httpServletRequest.getParameter("username"); 21 | 22 | if (!StringUtils.isNullOrEmpty(username)) { 23 | log.warn(username + " failed authentication: " + e); 24 | } else { 25 | log.warn(e); 26 | } 27 | 28 | httpServletResponse.sendRedirect("/signin?error=true"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/MockReviewComponent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web; 2 | 3 | import com.structurizr.onpremises.component.review.ReviewComponent; 4 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 5 | import com.structurizr.onpremises.domain.User; 6 | import com.structurizr.onpremises.domain.review.Review; 7 | import com.structurizr.onpremises.domain.review.ReviewType; 8 | import com.structurizr.onpremises.domain.review.Session; 9 | 10 | import java.util.Collection; 11 | 12 | public class MockReviewComponent implements ReviewComponent { 13 | 14 | @Override 15 | public Review createReview(User user, Long workspaceId, String[] files, ReviewType type) { 16 | return null; 17 | } 18 | 19 | @Override 20 | public Collection getReviews() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public Review getReview(String reviewId) { 26 | return null; 27 | } 28 | 29 | @Override 30 | public void submitReview(String reviewId, Session reviewSession) { 31 | } 32 | 33 | @Override 34 | public InputStreamAndContentLength getDiagram(String reviewId, String filename) { 35 | return null; 36 | } 37 | 38 | @Override 39 | public void lockReview(String reviewId) { 40 | } 41 | 42 | @Override 43 | public void unlockReview(String reviewId) { 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceVersion.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.util.StringUtils; 4 | 5 | import java.util.Date; 6 | 7 | public final class WorkspaceVersion { 8 | 9 | public static final String LATEST_VERSION = ""; 10 | 11 | private static final String VERSION_IDENTIFIER_REGEX = "[0-9a-zA-Z+-:_.]*"; 12 | 13 | private String versionId; 14 | private final Date lastModifiedDate; 15 | 16 | public WorkspaceVersion(String versionId, Date lastModifiedDate) { 17 | this.versionId = versionId; 18 | this.lastModifiedDate = lastModifiedDate; 19 | } 20 | 21 | public String getVersionId() { 22 | return versionId; 23 | } 24 | 25 | public void clearVersionId() { 26 | this.versionId = null; 27 | } 28 | 29 | public Date getLastModifiedDate() { 30 | return lastModifiedDate; 31 | } 32 | 33 | public static void validateVersionIdentifier(String identifier) { 34 | if (!StringUtils.isNullOrEmpty(identifier) && !isValidBVersionIdentifier(identifier)) { 35 | throw new IllegalArgumentException("The version identifier \"" + identifier + "\" is invalid"); 36 | } 37 | } 38 | 39 | public static boolean isValidBVersionIdentifier(String identifier) { 40 | return identifier.matches(VERSION_IDENTIFIER_REGEX); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceDao.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.onpremises.domain.Image; 4 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 5 | import com.structurizr.onpremises.domain.User; 6 | 7 | import java.io.File; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | interface WorkspaceDao { 12 | 13 | List getWorkspaceIds(); 14 | 15 | WorkspaceMetaData getWorkspaceMetaData(long workspaceId); 16 | 17 | void putWorkspaceMetaData(WorkspaceMetaData workspaceMetaData); 18 | 19 | long createWorkspace(User user); 20 | 21 | boolean deleteBranch(long workspaceId, String branch); 22 | 23 | boolean deleteWorkspace(long workspaceId); 24 | 25 | String getWorkspace(long workspaceId, String branch, String version); 26 | 27 | void putWorkspace(WorkspaceMetaData workspaceMetaData, String json, String branch); 28 | 29 | List getWorkspaceVersions(long workspaceId, String branch, int maxVersions); 30 | 31 | List getWorkspaceBranches(long workspaceId); 32 | 33 | boolean putImage(long workspaceId, String filename, File file); 34 | 35 | List getImages(long workspaceId); 36 | 37 | InputStreamAndContentLength getImage(long workspaceId, String filename); 38 | 39 | boolean deleteImages(long workspaceId); 40 | 41 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/Document.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | final class Document { 4 | 5 | private String url; 6 | private String workspace; 7 | private String name; 8 | private String description; 9 | private String type; 10 | private String content; 11 | 12 | Document() { 13 | } 14 | 15 | public String getUrl() { 16 | return url; 17 | } 18 | 19 | public void setUrl(String url) { 20 | this.url = url; 21 | } 22 | 23 | public String getWorkspace() { 24 | return workspace; 25 | } 26 | 27 | public void setWorkspace(String workspace) { 28 | this.workspace = workspace; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | public String getDescription() { 40 | return description; 41 | } 42 | 43 | public void setDescription(String description) { 44 | this.description = description; 45 | } 46 | 47 | public String getType() { 48 | return type; 49 | } 50 | 51 | public void setType(String type) { 52 | this.type = type; 53 | } 54 | 55 | public String getContent() { 56 | return content; 57 | } 58 | 59 | public void setContent(String content) { 60 | this.content = content; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-security-ldap.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/util/Version.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.util; 2 | 3 | import java.io.InputStream; 4 | import java.text.DateFormat; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.Properties; 8 | 9 | public final class Version { 10 | 11 | private static final String BUILD_PROPERTIES_FILENAME = "build.properties"; 12 | private static final String BUILD_VERSION_KEY = "build.number"; 13 | private static final String BUILD_TIMESTAMP_KEY = "build.timestamp"; 14 | 15 | private static String version; 16 | private static Date buildTimestamp; 17 | 18 | static { 19 | try { 20 | Properties buildProperties = new Properties(); 21 | InputStream in = Version.class.getClassLoader().getResourceAsStream(BUILD_PROPERTIES_FILENAME); 22 | DateFormat format = new SimpleDateFormat(DateUtils.ISO_DATE_TIME_FORMAT); 23 | if (in != null) { 24 | buildProperties.load(in); 25 | version = buildProperties.getProperty(BUILD_VERSION_KEY); 26 | buildTimestamp = format.parse(buildProperties.getProperty(BUILD_TIMESTAMP_KEY)); 27 | in.close(); 28 | } 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | public String getBuildNumber() { 35 | return version; 36 | } 37 | 38 | public Date getBuildTimestamp() { 39 | return buildTimestamp; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/component/workspace/WorkspaceVersionTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | public class WorkspaceVersionTests { 9 | 10 | @Test 11 | void isValidBVersionIdentifier() { 12 | assertTrue(WorkspaceVersion.isValidBVersionIdentifier("1234567890")); // local 13 | assertTrue(WorkspaceVersion.isValidBVersionIdentifier("3sL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo")); // aws 14 | assertTrue(WorkspaceVersion.isValidBVersionIdentifier("h_rKc7NTAmEyxFDB0p4CkCyg_y2uHmO.")); // aws 15 | assertTrue(WorkspaceVersion.isValidBVersionIdentifier("2024-09-07T16:34:45.7048862Z")); // azure 16 | 17 | assertFalse(WorkspaceVersion.isValidBVersionIdentifier(" 51 | 52 | 53 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/applicationContext-security-file.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/component/workspace/MockWorkspaceDao.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.onpremises.domain.Image; 4 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 5 | import com.structurizr.onpremises.domain.User; 6 | 7 | import java.io.File; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | public class MockWorkspaceDao implements WorkspaceDao { 12 | 13 | @Override 14 | public List getWorkspaceIds() { 15 | return null; 16 | } 17 | 18 | @Override 19 | public WorkspaceMetaData getWorkspaceMetaData(long workspaceId) { 20 | return null; 21 | } 22 | 23 | @Override 24 | public void putWorkspaceMetaData(WorkspaceMetaData workspaceMetaData) { 25 | 26 | } 27 | 28 | @Override 29 | public long createWorkspace(User user) { 30 | return 0; 31 | } 32 | 33 | @Override 34 | public boolean deleteBranch(long workspaceId, String branch) { 35 | return false; 36 | } 37 | 38 | @Override 39 | public boolean deleteWorkspace(long workspaceId) { 40 | return false; 41 | } 42 | 43 | @Override 44 | public String getWorkspace(long workspaceId, String branch, String version) { 45 | return null; 46 | } 47 | 48 | @Override 49 | public void putWorkspace(WorkspaceMetaData workspaceMetaData, String json, String branch) { 50 | 51 | } 52 | 53 | @Override 54 | public List getWorkspaceVersions(long workspaceId, String branch, int maxVersions) { 55 | return List.of(); 56 | } 57 | 58 | @Override 59 | public List getWorkspaceBranches(long workspaceId) { 60 | return List.of(); 61 | } 62 | 63 | @Override 64 | public boolean putImage(long workspaceId, String filename, File file) { 65 | return false; 66 | } 67 | 68 | @Override 69 | public List getImages(long workspaceId) { 70 | return null; 71 | } 72 | 73 | @Override 74 | public InputStreamAndContentLength getImage(long workspaceId, String filename) { 75 | return null; 76 | } 77 | 78 | @Override 79 | public boolean deleteImages(long workspaceId) { 80 | return false; 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/explore/ExploreController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.explore; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Controller 14 | public class ExploreController extends AbstractWorkspaceController { 15 | 16 | private static final String VIEW = "explore"; 17 | 18 | @RequestMapping(value = "/share/{workspaceId}/explore", method = RequestMethod.GET) 19 | public String showPublicExplorePage( 20 | @PathVariable("workspaceId") long workspaceId, 21 | ModelMap model 22 | ) { 23 | return showPublicView(VIEW, workspaceId, model, true); 24 | } 25 | 26 | @RequestMapping(value = "/share/{workspaceId}/{token}/explore", method = RequestMethod.GET) 27 | public String showSharedExplorePage( 28 | @PathVariable("workspaceId") long workspaceId, 29 | @PathVariable("token") String token, 30 | ModelMap model 31 | ) { 32 | return showSharedView(VIEW, workspaceId, token, model, true); 33 | } 34 | 35 | @RequestMapping(value = "/workspace/{workspaceId}/explore", method = RequestMethod.GET) 36 | @PreAuthorize("isAuthenticated()") 37 | public String showAuthenticatedExplorePage( 38 | @PathVariable("workspaceId") long workspaceId, 39 | @RequestParam(required = false) String branch, 40 | @RequestParam(required = false) String version, 41 | ModelMap model 42 | ) { 43 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 44 | if (workspaceMetaData == null) { 45 | return show404Page(model); 46 | } 47 | 48 | return showAuthenticatedView(VIEW, workspaceMetaData, branch, version, model, true, false); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/explore/ModelController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.explore; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Controller 14 | public class ModelController extends AbstractWorkspaceController { 15 | 16 | private static final String VIEW = "model"; 17 | 18 | @RequestMapping(value = "/share/{workspaceId}/explore/model", method = RequestMethod.GET) 19 | public String showPublicModel( 20 | @PathVariable("workspaceId") long workspaceId, 21 | ModelMap model 22 | ) { 23 | 24 | return showPublicView(VIEW, workspaceId, model, true); 25 | } 26 | 27 | @RequestMapping(value = "/share/{workspaceId}/{token}/explore/model", method = RequestMethod.GET) 28 | public String showSharedModel( 29 | @PathVariable("workspaceId") long workspaceId, 30 | @PathVariable("token") String token, 31 | ModelMap model 32 | ) { 33 | 34 | return showSharedView(VIEW, workspaceId, token, model, true); 35 | } 36 | 37 | @RequestMapping(value = "/workspace/{workspaceId}/explore/model", method = RequestMethod.GET) 38 | @PreAuthorize("isAuthenticated()") 39 | public String showAuthenticatedModel( 40 | @PathVariable("workspaceId") long workspaceId, 41 | @RequestParam(required = false) String branch, 42 | @RequestParam(required = false) String version, 43 | ModelMap model 44 | ) { 45 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 46 | if (workspaceMetaData == null) { 47 | return show404Page(model); 48 | } 49 | 50 | return showAuthenticatedView(VIEW, workspaceMetaData, branch, version, model, true, false); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/integrationTest/java/com/structurizr/onpremises/component/search/ElasticSearchComponentImplTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | 4 | import com.structurizr.onpremises.configuration.Configuration; 5 | import com.structurizr.onpremises.configuration.StructurizrProperties; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.testcontainers.elasticsearch.ElasticsearchContainer; 11 | 12 | import java.util.Properties; 13 | 14 | public class ElasticSearchComponentImplTests extends AbstractSearchComponentTests { 15 | 16 | private static ElasticsearchContainer elasticsearchContainer; 17 | private ElasticSearchComponentImpl searchComponent; 18 | 19 | @BeforeAll 20 | public static void startElasticsearchTestContainer() { 21 | elasticsearchContainer = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.17.5") 22 | .withExposedPorts(9200); 23 | elasticsearchContainer.start(); 24 | } 25 | 26 | @AfterAll 27 | public static void stopElasticsearchTestContainer() { 28 | elasticsearchContainer.stop(); 29 | } 30 | 31 | @BeforeEach 32 | public void setUp() { 33 | Properties properties = new Properties(); 34 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_HOST, "localhost"); 35 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_PORT, "" + elasticsearchContainer.getFirstMappedPort()); 36 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_PROTOCOL, "http"); 37 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_USERNAME, ""); 38 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_PASSWORD, ""); 39 | Configuration.init(properties); 40 | 41 | searchComponent = new ElasticSearchComponentImpl(); 42 | searchComponent.async = false; // disable async indexing for testing 43 | searchComponent.setIndexName("structurizr-test"); 44 | searchComponent.start(); 45 | } 46 | 47 | @AfterEach 48 | public void tearDown() { 49 | searchComponent.stop(); 50 | } 51 | 52 | @Override 53 | protected SearchComponent getSearchComponent() { 54 | return searchComponent; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/domain/review/Session.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.domain.review; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | 8 | import java.io.StringWriter; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class Session { 13 | 14 | private String author; 15 | private String userId; 16 | private List comments = new ArrayList<>(); 17 | 18 | Session() { 19 | } 20 | 21 | public String getAuthor() { 22 | return author; 23 | } 24 | 25 | public void setAuthor(String author) { 26 | this.author = author; 27 | } 28 | 29 | public String getUserId() { 30 | return userId; 31 | } 32 | 33 | public void setUserId(String userId) { 34 | this.userId = userId; 35 | } 36 | 37 | public List getComments() { 38 | return comments; 39 | } 40 | 41 | public void setComments(List comments) { 42 | this.comments = comments; 43 | } 44 | 45 | public static String toJson(Session reviewSession) throws Exception { 46 | ObjectMapper objectMapper = new ObjectMapper(); 47 | objectMapper.enable(SerializationFeature.INDENT_OUTPUT); 48 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 49 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); 50 | objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); 51 | objectMapper.disable(SerializationFeature.INDENT_OUTPUT); 52 | 53 | StringWriter writer = new StringWriter(); 54 | writer.write(objectMapper.writeValueAsString(reviewSession)); 55 | 56 | writer.flush(); 57 | writer.close(); 58 | 59 | return writer.toString(); 60 | } 61 | 62 | public static Session fromJson(String json) throws Exception { 63 | ObjectMapper objectMapper = new ObjectMapper(); 64 | objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); 65 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 66 | 67 | return objectMapper.readValue(json, Session.class); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/diagrams/DiagramEditorController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.diagrams; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceEditorController; 5 | import com.structurizr.util.StringUtils; 6 | import com.structurizr.view.PaperSize; 7 | import org.springframework.security.access.prepost.PreAuthorize; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.ModelMap; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | 15 | @Controller 16 | public class DiagramEditorController extends AbstractWorkspaceEditorController { 17 | 18 | private static final String VIEW = "diagrams"; 19 | 20 | @RequestMapping(value = "/workspace/{workspaceId}/diagram-editor", method = RequestMethod.GET) 21 | @PreAuthorize("isAuthenticated()") 22 | public String showAuthenticatedDiagramEditor( 23 | @PathVariable("workspaceId") long workspaceId, 24 | @RequestParam(required = false) String branch, 25 | @RequestParam(required = false) String version, 26 | ModelMap model 27 | ) { 28 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 29 | if (workspaceMetaData == null) { 30 | return show404Page(model); 31 | } 32 | 33 | model.addAttribute("publishThumbnails", StringUtils.isNullOrEmpty(branch) && StringUtils.isNullOrEmpty(version)); 34 | model.addAttribute("createReviews", true); 35 | model.addAttribute("quickNavigationPath", "diagram-editor"); 36 | model.addAttribute("paperSizes", PaperSize.getOrderedPaperSizes()); 37 | 38 | if (!workspaceMetaData.hasNoUsersConfigured() && !workspaceMetaData.isWriteUser(getUser())) { 39 | if (workspaceMetaData.isReadUser(getUser())) { 40 | return showError("workspace-is-readonly", model); 41 | } else { 42 | return show404Page(model); 43 | } 44 | } 45 | 46 | return lockWorkspaceAndShowAuthenticatedView(VIEW, workspaceMetaData, branch, version, model, false); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # - this script merges the contents of the structurizr/ui repository into this directory, 4 | # - this has only been tested on MacOS 5 | 6 | export STRUCTURIZR_BUILD_NUMBER=$1 7 | export STRUCTURIZR_UI_DIR=../structurizr-ui 8 | export STRUCTURIZR_ONPREMISES_DIR=./structurizr-onpremises 9 | 10 | rm -rf $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/bootstrap-icons 11 | rm -rf $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/css 12 | rm -rf $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/html 13 | rm -rf $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/img 14 | rm -rf $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/js 15 | 16 | # JavaScript 17 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/js 18 | cp -a $STRUCTURIZR_UI_DIR/src/js/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/js/ 19 | 20 | if [[ $STRUCTURIZR_BUILD_NUMBER != "" ]] 21 | then 22 | for file in $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/js/structurizr*.js 23 | do 24 | filename="${file%.*}" 25 | 26 | if [[ $file == *structurizr-embed.js ]] 27 | then 28 | echo "Skipping $filename" 29 | else 30 | echo "Renaming $filename-$STRUCTURIZR_BUILD_NUMBER.js" 31 | mv "$filename.js" "$filename-$STRUCTURIZR_BUILD_NUMBER.js" 32 | fi 33 | done 34 | fi 35 | 36 | # CSS 37 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/css 38 | cp -a $STRUCTURIZR_UI_DIR/src/css/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/css 39 | 40 | # Images 41 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/img 42 | cp -a $STRUCTURIZR_UI_DIR/src/img/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/img 43 | 44 | # Bootstrap icons 45 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/bootstrap-icons 46 | cp $STRUCTURIZR_UI_DIR/src/bootstrap-icons/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/bootstrap-icons 47 | 48 | # HTML (offline exports) 49 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/html 50 | cp $STRUCTURIZR_UI_DIR/src/html/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/static/html 51 | 52 | # JSP fragments 53 | cp -a $STRUCTURIZR_UI_DIR/src/fragments/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/WEB-INF/fragments 54 | 55 | # JSP 56 | cp $STRUCTURIZR_UI_DIR/src/jsp/* $STRUCTURIZR_ONPREMISES_DIR/src/main/webapp/WEB-INF/views 57 | 58 | # Java 59 | mkdir -p $STRUCTURIZR_ONPREMISES_DIR/src/main/java/com/structurizr/util/ 60 | cp $STRUCTURIZR_UI_DIR/src/java/com/structurizr/util/DslTemplate.java $STRUCTURIZR_ONPREMISES_DIR/src/main/java/com/structurizr/util/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .structurizr 2 | docs/structurizr.properties 3 | docs/workspace.json 4 | 5 | .gradle/* 6 | build/* 7 | structurizr-onpremises/build/* 8 | structurizr-onpremises-plugin/build/* 9 | .idea/* 10 | **/structurizr.log 11 | 12 | structurizr-onpremises/src/main/webapp/static/bootstrap-icons/* 13 | structurizr-onpremises/src/main/webapp/static/css/* 14 | structurizr-onpremises/src/main/webapp/static/html/* 15 | structurizr-onpremises/src/main/webapp/static/img/* 16 | structurizr-onpremises/src/main/webapp/static/js/* 17 | 18 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/graphviz.jspf 19 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/progress-message.jspf 20 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/quick-navigation.jspf 21 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/tooltip.jspf 22 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/diagrams/* 23 | !structurizr-onpremises/src/main/webapp/WEB-INF/fragments/diagrams/publish.jspf 24 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/decisions/* 25 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/dsl/* 26 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/load-via-inline.jspf 27 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/load-via-api.jspf 28 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/javascript.jspf 29 | structurizr-onpremises/src/main/webapp/WEB-INF/fragments/workspace/client-side-encryption-passphrase-modal.jspf 30 | 31 | structurizr-onpremises/src/main/webapp/WEB-INF/views/404.jsp 32 | structurizr-onpremises/src/main/webapp/WEB-INF/views/500.jsp 33 | structurizr-onpremises/src/main/webapp/WEB-INF/views/error.jsp 34 | structurizr-onpremises/src/main/webapp/WEB-INF/views/diagrams.jsp 35 | structurizr-onpremises/src/main/webapp/WEB-INF/views/decisions.jsp 36 | structurizr-onpremises/src/main/webapp/WEB-INF/views/dependencies.jsp 37 | structurizr-onpremises/src/main/webapp/WEB-INF/views/documentation.jsp 38 | structurizr-onpremises/src/main/webapp/WEB-INF/views/explore.jsp 39 | structurizr-onpremises/src/main/webapp/WEB-INF/views/graph.jsp 40 | structurizr-onpremises/src/main/webapp/WEB-INF/views/inspections.jsp 41 | structurizr-onpremises/src/main/webapp/WEB-INF/views/model.jsp 42 | structurizr-onpremises/src/main/webapp/WEB-INF/views/tree.jsp 43 | structurizr-onpremises/src/main/webapp/WEB-INF/views/review-create.jsp 44 | structurizr-onpremises/src/main/webapp/WEB-INF/views/review.jsp 45 | 46 | structurizr-onpremises/src/main/java/com/structurizr/util/DslTemplate.java -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/ElasticsearchConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import com.structurizr.util.StringUtils; 4 | 5 | import java.util.Properties; 6 | 7 | import static com.structurizr.onpremises.configuration.StructurizrProperties.*; 8 | 9 | class ElasticsearchConfigurer extends Configurer { 10 | 11 | ElasticsearchConfigurer(Properties properties) { 12 | super(properties); 13 | } 14 | 15 | private static final String PROTOCOL_SEPARATOR = "://"; 16 | private static final String PORT_SEPARATOR = ":"; 17 | 18 | private static final String DEFAULT_PROTOCOL = "http"; 19 | private static final String DEFAULT_HOST = "localhost"; 20 | private static final String DEFAULT_PORT = "9200"; 21 | 22 | void apply() { 23 | setDefault(ELASTICSEARCH_PROTOCOL, DEFAULT_PROTOCOL); 24 | setDefault(ELASTICSEARCH_HOST, DEFAULT_HOST); 25 | setDefault(ELASTICSEARCH_PORT, DEFAULT_PORT); 26 | setDefault(ELASTICSEARCH_USERNAME, ""); 27 | setDefault(ELASTICSEARCH_PASSWORD, ""); 28 | 29 | String endpoint = properties.getProperty(ELASTICSEARCH_ENDPOINT); 30 | if (!StringUtils.isNullOrEmpty(endpoint)) { 31 | // setting elasticsearch.endpoint will override: 32 | // - elasticsearch.protocol 33 | // - elasticsearch.host 34 | // - elasticsearch.port 35 | String protocol = endpoint.substring(0, endpoint.indexOf(PROTOCOL_SEPARATOR)); 36 | String host = endpoint.substring(endpoint.indexOf(PROTOCOL_SEPARATOR) + PROTOCOL_SEPARATOR.length()); 37 | host = host.substring(0, host.indexOf(PORT_SEPARATOR)); 38 | String port = endpoint.substring(endpoint.lastIndexOf(PORT_SEPARATOR) + PORT_SEPARATOR.length()); 39 | 40 | properties.setProperty(ELASTICSEARCH_PROTOCOL, protocol); 41 | properties.setProperty(ELASTICSEARCH_HOST, host); 42 | properties.setProperty(ELASTICSEARCH_PORT, port); 43 | } else { 44 | // set elasticsearch.endpoint from the separate protocol, host, and port properties 45 | String protocol = properties.getProperty(ELASTICSEARCH_PROTOCOL); 46 | String host = properties.getProperty(ELASTICSEARCH_HOST); 47 | String port = properties.getProperty(ELASTICSEARCH_PORT); 48 | endpoint = String.format("%s://%s:%s", protocol, host, port); 49 | properties.setProperty(ELASTICSEARCH_ENDPOINT, endpoint); 50 | } 51 | 52 | } 53 | 54 | 55 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/AbstractWorkspaceEditorController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceComponentException; 4 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 5 | import com.structurizr.onpremises.util.RandomGuidGenerator; 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | import org.springframework.ui.ModelMap; 9 | 10 | public abstract class AbstractWorkspaceEditorController extends AbstractWorkspaceController { 11 | 12 | private static final Log log = LogFactory.getLog(AbstractWorkspaceEditorController.class); 13 | 14 | private static final String AGENT = "structurizr-onpremises"; 15 | 16 | protected final String lockWorkspaceAndShowAuthenticatedView(String view, WorkspaceMetaData workspaceMetaData, String branch, String version, ModelMap model, boolean showHeaderAndFooter) { 17 | boolean success = false; 18 | String agent = AGENT + "/" + view + "/" + new RandomGuidGenerator().generate(); 19 | 20 | try { 21 | success = workspaceComponent.lockWorkspace(workspaceMetaData.getId(), getUser().getUsername(), agent); 22 | } catch (WorkspaceComponentException e) { 23 | log.error(e); 24 | } 25 | 26 | if (!success) { 27 | if (workspaceMetaData.isLocked()) { 28 | model.addAttribute("showHeader", true); 29 | model.addAttribute("showFooter", true); 30 | addCommonAttributes(model, "Workspace locked", true); 31 | model.addAttribute("workspace", workspaceMetaData); 32 | 33 | return showError("workspace-locked", model); 34 | } else { 35 | workspaceMetaData.setEditable(false); 36 | model.addAttribute("showHeader", true); 37 | model.addAttribute("showFooter", true); 38 | addCommonAttributes(model, "Workspace could not be locked", true); 39 | model.addAttribute("workspace", workspaceMetaData); 40 | 41 | return showError("workspace-could-not-be-locked", model); 42 | } 43 | } else { 44 | workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceMetaData.getId()); // refresh metadata 45 | model.addAttribute("userAgent", agent); 46 | return showAuthenticatedView(view, workspaceMetaData, branch, version, model, showHeaderAndFooter, true); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/management/DeleteBranchController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.management; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceBranch; 4 | import com.structurizr.onpremises.component.workspace.WorkspaceComponentException; 5 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 6 | import com.structurizr.onpremises.configuration.Configuration; 7 | import com.structurizr.onpremises.configuration.Features; 8 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 9 | import org.apache.commons.logging.Log; 10 | import org.apache.commons.logging.LogFactory; 11 | import org.springframework.security.access.prepost.PreAuthorize; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.ModelMap; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | 18 | @Controller 19 | public class DeleteBranchController extends AbstractWorkspaceController { 20 | 21 | private static final Log log = LogFactory.getLog(DeleteBranchController.class); 22 | 23 | @RequestMapping(value="/workspace/{workspaceId}/branch/{branch}/delete", method = RequestMethod.POST) 24 | @PreAuthorize("isAuthenticated()") 25 | public String deleteWorkspace(@PathVariable("workspaceId")long workspaceId, 26 | @PathVariable("branch")String branch, 27 | ModelMap model) { 28 | if (!Configuration.getInstance().isFeatureEnabled(Features.WORKSPACE_BRANCHES)) { 29 | return show404Page(model); 30 | } 31 | 32 | if (WorkspaceBranch.isMainBranch(branch)) { 33 | return show404Page(model); 34 | } 35 | 36 | try { 37 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 38 | if (workspaceMetaData != null) { 39 | if (workspaceMetaData.hasUsersConfigured() && !workspaceMetaData.isWriteUser(getUser())) { 40 | return show404Page(model); 41 | } 42 | 43 | workspaceComponent.deleteBranch(workspaceId, branch); 44 | } else { 45 | return show404Page(model); 46 | } 47 | } catch (WorkspaceComponentException e) { 48 | log.error(e); 49 | } 50 | 51 | return "redirect:/workspace/" + workspaceId; 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/json/JsonController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.json; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.ModelMap; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | 13 | @Controller 14 | public class JsonController extends AbstractWorkspaceController { 15 | 16 | private static final Log log = LogFactory.getLog(JsonController.class); 17 | private static final String VIEW = "json"; 18 | 19 | @RequestMapping(value = "/share/{workspaceId}/json", method = RequestMethod.GET) 20 | public String showPublicJson( 21 | @PathVariable("workspaceId") long workspaceId, 22 | ModelMap model 23 | ) { 24 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 25 | if (workspaceMetaData == null) { 26 | return show404Page(model); 27 | } 28 | 29 | try { 30 | String workspaceAsJson = workspaceComponent.getWorkspace(workspaceId, null, null); 31 | model.addAttribute("json", workspaceAsJson); 32 | } catch (Exception e) { 33 | log.error(e); 34 | throw new RuntimeException(e); 35 | } 36 | 37 | return showPublicView(VIEW, workspaceId, model, false); 38 | } 39 | 40 | @RequestMapping(value = "/share/{workspaceId}/{token}/json", method = RequestMethod.GET) 41 | public String showSharedJson( 42 | @PathVariable("workspaceId") long workspaceId, 43 | @PathVariable("token") String token, 44 | ModelMap model 45 | ) { 46 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 47 | if (workspaceMetaData == null) { 48 | return show404Page(model); 49 | } 50 | 51 | try { 52 | String workspaceAsJson = workspaceComponent.getWorkspace(workspaceId, null, null); 53 | model.addAttribute("json", workspaceAsJson); 54 | } catch (Exception e) { 55 | log.error(e); 56 | throw new RuntimeException(e); 57 | } 58 | 59 | return showSharedView(VIEW, workspaceId, token, model, false); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/security/AuthenticationSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.security; 2 | 3 | import com.structurizr.onpremises.domain.AuthenticationMethod; 4 | import com.structurizr.onpremises.domain.User; 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 9 | import org.springframework.security.web.savedrequest.HttpSessionRequestCache; 10 | import org.springframework.security.web.savedrequest.SavedRequest; 11 | 12 | import jakarta.servlet.ServletException; 13 | import jakarta.servlet.http.Cookie; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { 19 | 20 | private static final Log log = LogFactory.getLog(AuthenticationSuccessHandler.class); 21 | 22 | private static final int USERNAME_COOKIE_EXPIRY_IN_SECONDS = 60 * 60 * 24 * 30; // 30 days 23 | 24 | @Override 25 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { 26 | User user = SecurityUtils.getUser(authentication); 27 | log.info(user.getUsername() + " successfully authenticated"); 28 | 29 | if (user.getAuthenticationMethod() == AuthenticationMethod.LOCAL) { 30 | Cookie cookie = new Cookie("structurizr.username", user.getUsername()); 31 | cookie.setSecure(true); 32 | cookie.setHttpOnly(true); 33 | String rememberUsername = request.getParameter("rememberUsername"); 34 | if (rememberUsername != null && "true".equals(rememberUsername)) { 35 | cookie.setMaxAge(USERNAME_COOKIE_EXPIRY_IN_SECONDS); 36 | } else { 37 | cookie.setMaxAge(0); 38 | } 39 | httpServletResponse.addCookie(cookie); 40 | } 41 | 42 | SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, httpServletResponse); 43 | String hash = request.getParameter("hash"); 44 | if (hash == null) { 45 | hash = ""; 46 | } 47 | 48 | if (savedRequest != null) { 49 | httpServletResponse.sendRedirect(savedRequest.getRedirectUrl() + hash); 50 | } else { 51 | httpServletResponse.sendRedirect("/dashboard"); 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/explore/TreeController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.explore; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Controller 14 | public class TreeController extends AbstractWorkspaceController { 15 | 16 | private static final String VIEW = "tree"; 17 | 18 | @RequestMapping(value = "/share/{workspaceId}/explore/tree", method = RequestMethod.GET) 19 | public String showPublicTree( 20 | @PathVariable("workspaceId") long workspaceId, 21 | @RequestParam(required = false) String view, 22 | ModelMap model 23 | ) { 24 | model.addAttribute("view", view); 25 | 26 | return showPublicView(VIEW, workspaceId, model, true); 27 | } 28 | 29 | @RequestMapping(value = "/share/{workspaceId}/{token}/explore/tree", method = RequestMethod.GET) 30 | public String showSharedTree( 31 | @PathVariable("workspaceId") long workspaceId, 32 | @RequestParam(required = false) String view, 33 | @PathVariable("token") String token, 34 | ModelMap model 35 | ) { 36 | model.addAttribute("view", view); 37 | 38 | return showSharedView(VIEW, workspaceId, token, model, true); 39 | } 40 | 41 | @RequestMapping(value = "/workspace/{workspaceId}/explore/tree", method = RequestMethod.GET) 42 | @PreAuthorize("isAuthenticated()") 43 | public String showAuthenticatedTree( 44 | @PathVariable("workspaceId") long workspaceId, 45 | @RequestParam(required = false) String branch, 46 | @RequestParam(required = false) String version, 47 | @RequestParam(required = false) String view, 48 | ModelMap model 49 | ) { 50 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 51 | if (workspaceMetaData == null) { 52 | return show404Page(model); 53 | } 54 | 55 | model.addAttribute("view", view); 56 | 57 | return showAuthenticatedView(VIEW, workspaceMetaData, branch, version, model, true, false); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/explore/GraphController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.explore; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.ModelMap; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @Controller 14 | public class GraphController extends AbstractWorkspaceController { 15 | 16 | private static final String VIEW = "graph"; 17 | 18 | @RequestMapping(value = "/share/{workspaceId}/explore/graph", method = RequestMethod.GET) 19 | public String showPublicGraph( 20 | @PathVariable("workspaceId") long workspaceId, 21 | @RequestParam(required = false) String view, 22 | ModelMap model 23 | ) { 24 | model.addAttribute("view", view); 25 | 26 | return showPublicView(VIEW, workspaceId, model, true); 27 | } 28 | 29 | @RequestMapping(value = "/share/{workspaceId}/{token}/explore/graph", method = RequestMethod.GET) 30 | public String showSharedGraph( 31 | @PathVariable("workspaceId") long workspaceId, 32 | @RequestParam(required = false) String view, 33 | @PathVariable("token") String token, 34 | ModelMap model 35 | ) { 36 | model.addAttribute("view", view); 37 | 38 | return showSharedView(VIEW, workspaceId, token, model, true); 39 | } 40 | 41 | @RequestMapping(value = "/workspace/{workspaceId}/explore/graph", method = RequestMethod.GET) 42 | @PreAuthorize("isAuthenticated()") 43 | public String showAuthenticatedGraph( 44 | @PathVariable("workspaceId") long workspaceId, 45 | @RequestParam(required = false) String branch, 46 | @RequestParam(required = false) String version, 47 | @RequestParam(required = false) String view, 48 | ModelMap model 49 | ) { 50 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 51 | if (workspaceMetaData == null) { 52 | return show404Page(model); 53 | } 54 | 55 | model.addAttribute("view", view); 56 | 57 | return showAuthenticatedView(VIEW, workspaceMetaData, branch, version, model, true, false); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/fragments/diagrams/publish.jspf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /structurizr-onpremises-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'signing' 4 | 5 | defaultTasks 'clean', 'compileJava', 'test' 6 | 7 | repositories { 8 | mavenCentral() 9 | mavenLocal() 10 | } 11 | 12 | dependencies { 13 | api "com.structurizr:structurizr-client:${structurizrVersion}" 14 | 15 | testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3' 16 | } 17 | 18 | compileJava.options.encoding = 'UTF-8' 19 | compileTestJava.options.encoding = 'UTF-8' 20 | 21 | sourceCompatibility = 17 22 | targetCompatibility = 17 23 | 24 | description = 'Plugin APIs for the Structurizr on-premises installation' 25 | group = 'com.structurizr' 26 | 27 | test { 28 | useJUnitPlatform() 29 | } 30 | 31 | java { 32 | withJavadocJar() 33 | withSourcesJar() 34 | } 35 | 36 | jar { 37 | manifest { 38 | attributes( 39 | 'Implementation-Title': description, 40 | 'Implementation-Version': version 41 | ) 42 | } 43 | } 44 | 45 | publishing { 46 | repositories { 47 | maven { 48 | name = "ossrh" 49 | url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 50 | credentials { 51 | username = findProperty('ossrhUsername') 52 | password = findProperty('ossrhPassword') 53 | } 54 | } 55 | } 56 | 57 | publications { 58 | mavenJava(MavenPublication) { 59 | from components.java 60 | 61 | pom { 62 | name = 'structurizr-onpremises-plugins' 63 | description = 'Plugin APIs for the Structurizr on-premises installation' 64 | url = 'https://github.com/structurizr/onpremises' 65 | 66 | scm { 67 | connection = 'scm:git:git://github.com/structurizr/onpremises.git' 68 | developerConnection = 'scm:git:git@github.com:structurizr/onpremises.git' 69 | url = 'https://github.com/structurizr/onpremises' 70 | } 71 | 72 | licenses { 73 | license { 74 | name = 'The Apache License, Version 2.0' 75 | url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id = "simon" 82 | name = "Simon Brown" 83 | email = "help@structurizr.com" 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | signing { 92 | sign publishing.publications.mavenJava 93 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/security/SignInController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.security; 2 | 3 | import com.structurizr.onpremises.web.AbstractController; 4 | import jakarta.servlet.Filter; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | import org.springframework.security.web.FilterChainProxy; 9 | import org.springframework.security.web.SecurityFilterChain; 10 | import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.ModelMap; 13 | import org.springframework.web.bind.annotation.CookieValue; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | 17 | @Controller 18 | public class SignInController extends AbstractController implements ApplicationContextAware { 19 | 20 | private static final String STRUCTURIZR_USERNAME_COOKIE_NAME = "structurizr.username"; 21 | private static final String FILTER_CHAIN_PROXY_BEAN_NAME = "org.springframework.security.filterChainProxy"; 22 | 23 | private ApplicationContext applicationContext; 24 | 25 | @RequestMapping(value = "/signin", method = RequestMethod.GET) 26 | public String getSignInPage(@CookieValue(value=STRUCTURIZR_USERNAME_COOKIE_NAME,defaultValue="") String username, ModelMap model) { 27 | if (username != null && username.length() > 0) { 28 | model.addAttribute("username", username); 29 | } 30 | 31 | // only show the "remember me" checkbox if the RememberMeAuthenticationFilter is configured 32 | boolean rememberMeEnabled = false; 33 | FilterChainProxy filterChainProxy = (FilterChainProxy)applicationContext.getBean(FILTER_CHAIN_PROXY_BEAN_NAME); 34 | if (filterChainProxy != null) { 35 | for (SecurityFilterChain filterChain : filterChainProxy.getFilterChains()) { 36 | for (Filter filter : filterChain.getFilters()) { 37 | if (filter instanceof RememberMeAuthenticationFilter) { 38 | rememberMeEnabled = true; 39 | } 40 | } 41 | } 42 | } 43 | 44 | model.addAttribute("rememberMeEnabled", rememberMeEnabled); 45 | addCommonAttributes(model, "Sign in", true); 46 | 47 | return "signin"; 48 | } 49 | 50 | @Override 51 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 52 | this.applicationContext = applicationContext; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/workspace/WorkspaceComponent.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.workspace; 2 | 3 | import com.structurizr.onpremises.domain.Image; 4 | import com.structurizr.onpremises.domain.InputStreamAndContentLength; 5 | import com.structurizr.onpremises.domain.User; 6 | 7 | import java.io.File; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | /** 12 | * Provides access to workspace data stored on the file system, Amazon Web Services S3, or Microsoft Azure Blob Storage. 13 | */ 14 | public interface WorkspaceComponent { 15 | 16 | Collection getWorkspaces() throws WorkspaceComponentException; 17 | 18 | Collection getWorkspaces(User user) throws WorkspaceComponentException; 19 | 20 | WorkspaceMetaData getWorkspaceMetaData(long workspaceId) throws WorkspaceComponentException; 21 | 22 | void putWorkspaceMetaData(WorkspaceMetaData workspaceMetaData) throws WorkspaceComponentException; 23 | 24 | String getWorkspace(long workspaceId, String branch, String version) throws WorkspaceComponentException; 25 | 26 | long createWorkspace(User user) throws WorkspaceComponentException; 27 | 28 | boolean deleteBranch(long workspaceId, String branch) throws WorkspaceComponentException; 29 | 30 | boolean deleteWorkspace(long workspaceId) throws WorkspaceComponentException; 31 | 32 | void putWorkspace(long workspaceId, String branch, String json) throws WorkspaceComponentException; 33 | 34 | List getWorkspaceVersions(long workspaceId, String branch) throws WorkspaceComponentException; 35 | 36 | List getWorkspaceBranches(long workspaceId) throws WorkspaceComponentException; 37 | 38 | boolean lockWorkspace(long workspaceId, String username, String agent) throws WorkspaceComponentException; 39 | 40 | boolean unlockWorkspace(long workspaceId) throws WorkspaceComponentException; 41 | 42 | boolean putImage(long workspaceId, String filename, File file) throws WorkspaceComponentException; 43 | 44 | List getImages(long workspaceId) throws WorkspaceComponentException; 45 | 46 | InputStreamAndContentLength getImage(long workspaceId, String diagramKey) throws WorkspaceComponentException; 47 | 48 | boolean deleteImages(long workspaceId) throws WorkspaceComponentException; 49 | 50 | void makeWorkspacePublic(long workspaceId) throws WorkspaceComponentException; 51 | 52 | void makeWorkspacePrivate(long workspaceId) throws WorkspaceComponentException; 53 | 54 | void shareWorkspace(long workspaceId) throws WorkspaceComponentException; 55 | 56 | void unshareWorkspace(long workspaceId) throws WorkspaceComponentException; 57 | 58 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/images.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Images

4 |

5 | Structurizr supports the ability to embed diagrams via a static PNG file via a regular image tag in HTML, Markdown, AsciiDoc, etc. 6 |
7 | See Structurizr on-premises - Embedding diagrams - PNG embed for more details. 8 |

9 |

10 | (back to workspace summary) 11 |

12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |

22 | ${image.name} 23 |
24 | 25 |
26 | ${image.sizeInKB} KB 27 |

28 |
29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 42 |

43 | No images have been published for this workspace. 44 |

45 |
46 |
47 |
48 |
49 | 50 | -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/management/DeleteWorkspaceController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.management; 2 | 3 | import com.structurizr.onpremises.component.search.SearchComponent; 4 | import com.structurizr.onpremises.component.search.SearchComponentException; 5 | import com.structurizr.onpremises.component.workspace.WorkspaceComponentException; 6 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 7 | import com.structurizr.onpremises.domain.User; 8 | import com.structurizr.onpremises.configuration.Configuration; 9 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.ui.ModelMap; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestMethod; 19 | 20 | @Controller 21 | public class DeleteWorkspaceController extends AbstractWorkspaceController { 22 | 23 | private static final Log log = LogFactory.getLog(DeleteWorkspaceController.class); 24 | 25 | private SearchComponent searchComponent; 26 | 27 | @Autowired 28 | public void setSearchComponent(SearchComponent searchComponent) { 29 | this.searchComponent = searchComponent; 30 | } 31 | 32 | @RequestMapping(value="/workspace/{workspaceId}/delete", method = RequestMethod.POST) 33 | @PreAuthorize("isAuthenticated()") 34 | public String deleteWorkspace(@PathVariable("workspaceId")long workspaceId, ModelMap model) { 35 | Configuration configuration = Configuration.getInstance(); 36 | User user = getUser(); 37 | 38 | try { 39 | WorkspaceMetaData workspace = workspaceComponent.getWorkspaceMetaData(workspaceId); 40 | if (workspace != null) { 41 | if (configuration.getAdminUsersAndRoles().isEmpty() || user.isAdmin()) { 42 | if (workspaceComponent.deleteWorkspace(workspaceId)) { 43 | try { 44 | searchComponent.delete(workspaceId); 45 | } catch (SearchComponentException e) { 46 | log.error(e); 47 | } 48 | } 49 | } 50 | } else { 51 | return show404Page(model); 52 | } 53 | } catch (WorkspaceComponentException e) { 54 | log.error(e); 55 | } 56 | 57 | return "redirect:/dashboard"; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/configuration/RedisConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import com.structurizr.util.StringUtils; 4 | 5 | import java.util.Properties; 6 | 7 | import static com.structurizr.onpremises.configuration.StructurizrProperties.*; 8 | 9 | class RedisConfigurer extends Configurer { 10 | 11 | private static final String PROTOCOL_SEPARATOR = "://"; 12 | private static final String PORT_SEPARATOR = ":"; 13 | 14 | private static final String DEFAULT_PROTOCOL = "redis"; 15 | private static final String DEFAULT_HOST = "localhost"; 16 | private static final String DEFAULT_PORT = "6379"; 17 | private static final String DEFAULT_DATABASE = "0"; 18 | private static final String DEFAULT_PASSWORD = ""; 19 | 20 | private static final String SECURE_PROTOCOL = "rediss"; 21 | 22 | RedisConfigurer(Properties properties) { 23 | super(properties); 24 | } 25 | 26 | void apply() { 27 | setDefault(REDIS_PROTOCOL, DEFAULT_PROTOCOL); 28 | setDefault(REDIS_HOST, DEFAULT_HOST); 29 | setDefault(REDIS_PORT, DEFAULT_PORT); 30 | setDefault(REDIS_PASSWORD, DEFAULT_PASSWORD); 31 | setDefault(REDIS_DATABASE, DEFAULT_DATABASE); 32 | 33 | String redisEndpoint = properties.getProperty(StructurizrProperties.REDIS_ENDPOINT); 34 | if (!StringUtils.isNullOrEmpty(redisEndpoint)) { 35 | // setting structurizr.redis.endpoint will override: 36 | // - structurizr.redis.protocol 37 | // - structurizr.redis.host 38 | // - structurizr.redis.port 39 | String protocol = redisEndpoint.substring(0, redisEndpoint.indexOf(PROTOCOL_SEPARATOR)); 40 | String host = redisEndpoint.substring(redisEndpoint.indexOf(PROTOCOL_SEPARATOR) + PROTOCOL_SEPARATOR.length()); 41 | host = host.substring(0, host.indexOf(PORT_SEPARATOR)); 42 | String port = redisEndpoint.substring(redisEndpoint.lastIndexOf(PORT_SEPARATOR) + PORT_SEPARATOR.length()); 43 | 44 | properties.setProperty(REDIS_PROTOCOL, protocol); 45 | properties.setProperty(REDIS_HOST, host); 46 | properties.setProperty(REDIS_PORT, port); 47 | } else { 48 | // set structurizr.redis.endpoint from the separate protocol, host, and port properties 49 | String protocol = properties.getProperty(REDIS_PROTOCOL); 50 | String host = properties.getProperty(REDIS_HOST); 51 | String port = properties.getProperty(REDIS_PORT); 52 | redisEndpoint = String.format("%s://%s:%s", protocol, host, port); 53 | properties.setProperty(REDIS_ENDPOINT, redisEndpoint); 54 | } 55 | 56 | // a protocol of rediss will set structurizr.redis.ssl to true 57 | // (this is used in applicationContext-session-redis.xml) 58 | properties.setProperty(REDIS_SSL, Boolean.toString(SECURE_PROTOCOL.equals(properties.getProperty(REDIS_PROTOCOL, DEFAULT_PROTOCOL)))); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/dsl/DslController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.dsl.DslUtils; 5 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 6 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 7 | import com.structurizr.util.WorkspaceUtils; 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.ModelMap; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | 16 | @Controller 17 | public class DslController extends AbstractWorkspaceController { 18 | 19 | private static final Log log = LogFactory.getLog(DslController.class); 20 | private static final String VIEW = "plaintext"; 21 | 22 | @RequestMapping(value = "/share/{workspaceId}/dsl", method = RequestMethod.GET) 23 | public String showPublicDsl( 24 | @PathVariable("workspaceId") long workspaceId, 25 | ModelMap model 26 | ) { 27 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 28 | if (workspaceMetaData == null) { 29 | return show404Page(model); 30 | } 31 | 32 | try { 33 | String workspaceAsJson = workspaceComponent.getWorkspace(workspaceId, null, null); 34 | Workspace workspace = WorkspaceUtils.fromJson(workspaceAsJson); 35 | String dsl = DslUtils.getDsl(workspace); 36 | 37 | model.addAttribute("text", dsl); 38 | } catch (Exception e) { 39 | log.error(e); 40 | throw new RuntimeException(e); 41 | } 42 | 43 | return showPublicView(VIEW, workspaceId, model, false); 44 | } 45 | 46 | @RequestMapping(value = "/share/{workspaceId}/{token}/dsl", method = RequestMethod.GET) 47 | public String showSharedDsl( 48 | @PathVariable("workspaceId") long workspaceId, 49 | @PathVariable("token") String token, 50 | ModelMap model 51 | ) { 52 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 53 | if (workspaceMetaData == null) { 54 | return show404Page(model); 55 | } 56 | 57 | try { 58 | String workspaceAsJson = workspaceComponent.getWorkspace(workspaceId, null, null); 59 | Workspace workspace = WorkspaceUtils.fromJson(workspaceAsJson); 60 | String dsl = DslUtils.getDsl(workspace); 61 | 62 | model.addAttribute("text", dsl); 63 | } catch (Exception e) { 64 | log.error(e); 65 | throw new RuntimeException(e); 66 | } 67 | 68 | return showSharedView(VIEW, workspaceId, token, model, false); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/web/workspace/management/CreateWorkspaceControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.management; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceComponentException; 4 | import com.structurizr.onpremises.domain.User; 5 | import com.structurizr.onpremises.configuration.Configuration; 6 | import com.structurizr.onpremises.configuration.StructurizrProperties; 7 | import com.structurizr.onpremises.web.ControllerTestsBase; 8 | import com.structurizr.onpremises.web.MockWorkspaceComponent; 9 | 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.ui.ModelMap; 13 | 14 | import java.util.Properties; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | 18 | public class CreateWorkspaceControllerTests extends ControllerTestsBase { 19 | 20 | private CreateWorkspaceController controller; 21 | private ModelMap model; 22 | 23 | @BeforeEach 24 | public void setUp() { 25 | controller = new CreateWorkspaceController(); 26 | model = new ModelMap(); 27 | clearUser(); 28 | Configuration.init(); 29 | } 30 | 31 | @Test 32 | public void createWorkspace_CreatesAWorkspace_WhenNoAdminUsersAreDefined() { 33 | Configuration.init(); 34 | setUser("user@example.com"); 35 | 36 | controller.setWorkspaceComponent(new MockWorkspaceComponent() { 37 | @Override 38 | public long createWorkspace(User user) throws WorkspaceComponentException { 39 | return 1; 40 | } 41 | }); 42 | String result = controller.createWorkspace(model); 43 | 44 | assertEquals("redirect:/workspace/1", result); 45 | } 46 | 47 | @Test 48 | public void createWorkspace_CreatesAWorkspace_WhenAnAdminUserIsDefined() { 49 | Properties properties = new Properties(); 50 | properties.setProperty(StructurizrProperties.ADMIN_USERS_AND_ROLES, "admin@example.com"); 51 | Configuration.init(properties); 52 | setUser("admin@example.com"); 53 | 54 | controller.setWorkspaceComponent(new MockWorkspaceComponent() { 55 | @Override 56 | public long createWorkspace(User user) throws WorkspaceComponentException { 57 | return 1; 58 | } 59 | }); 60 | String result = controller.createWorkspace(model); 61 | 62 | assertEquals("redirect:/workspace/1", result); 63 | } 64 | 65 | @Test 66 | public void createWorkspace_ReturnsThe404Page_WhenTheUserIsNotAnAdmin() { 67 | Properties properties = new Properties(); 68 | properties.setProperty(StructurizrProperties.ADMIN_USERS_AND_ROLES, "admin@example.com"); 69 | Configuration.init(properties); 70 | setUser("user@example.com"); 71 | 72 | controller.setWorkspaceComponent(new MockWorkspaceComponent() {}); 73 | String result = controller.createWorkspace(model); 74 | 75 | assertEquals("404", result); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/webapp/WEB-INF/views/reviews.jsp: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 |
29 |

Reviews

30 | 31 |

32 | (back to workspace summary) 33 |

34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |

45 | ${review.type} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | Workspace ${review.workspaceId} 57 |
58 |
59 | 60 |

61 |
62 |
63 | 64 | 65 |
66 |
67 | New review 68 |
69 | 70 |


71 | 72 |
73 |
74 | 75 |
76 |
77 |
78 |
79 |
80 | 81 |
82 |
-------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/configuration/ElasticsearchConfigurerTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Properties; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class ElasticsearchConfigurerTests { 10 | 11 | @Test 12 | void apply_WithDefaults() { 13 | Properties properties = new Properties(); 14 | new ElasticsearchConfigurer(properties).apply(); 15 | 16 | assertEquals("http", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PROTOCOL)); 17 | assertEquals("localhost", properties.getProperty(StructurizrProperties.ELASTICSEARCH_HOST)); 18 | assertEquals("9200", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PORT)); 19 | assertEquals("", properties.getProperty(StructurizrProperties.ELASTICSEARCH_USERNAME)); 20 | assertEquals("", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PASSWORD)); 21 | assertEquals("http://localhost:9200", properties.getProperty(StructurizrProperties.ELASTICSEARCH_ENDPOINT)); 22 | } 23 | 24 | @Test 25 | void apply_WithoutEndpoint() { 26 | Properties properties = new Properties(); 27 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_HOST, "example.com"); 28 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_PORT, "1234"); 29 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_USERNAME, "username"); 30 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_PASSWORD, "password"); 31 | new ElasticsearchConfigurer(properties).apply(); 32 | 33 | assertEquals("http", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PROTOCOL)); 34 | assertEquals("example.com", properties.getProperty(StructurizrProperties.ELASTICSEARCH_HOST)); 35 | assertEquals("1234", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PORT)); 36 | assertEquals("username", properties.getProperty(StructurizrProperties.ELASTICSEARCH_USERNAME)); 37 | assertEquals("password", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PASSWORD)); 38 | assertEquals("http://example.com:1234", properties.getProperty(StructurizrProperties.ELASTICSEARCH_ENDPOINT)); 39 | } 40 | 41 | @Test 42 | void apply_WithEndpoint() { 43 | Properties properties = new Properties(); 44 | properties.setProperty(StructurizrProperties.ELASTICSEARCH_ENDPOINT, "https://example.com:1234"); 45 | new ElasticsearchConfigurer(properties).apply(); 46 | 47 | assertEquals("https", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PROTOCOL)); 48 | assertEquals("example.com", properties.getProperty(StructurizrProperties.ELASTICSEARCH_HOST)); 49 | assertEquals("1234", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PORT)); 50 | assertEquals("", properties.getProperty(StructurizrProperties.ELASTICSEARCH_USERNAME)); 51 | assertEquals("", properties.getProperty(StructurizrProperties.ELASTICSEARCH_PASSWORD)); 52 | assertEquals("https://example.com:1234", properties.getProperty(StructurizrProperties.ELASTICSEARCH_ENDPOINT)); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/component/search/AbstractSearchComponentImpl.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.component.search; 2 | 3 | import com.structurizr.documentation.Decision; 4 | import com.structurizr.model.Component; 5 | import com.structurizr.model.Container; 6 | import com.structurizr.model.Element; 7 | import com.structurizr.model.SoftwareSystem; 8 | import com.structurizr.util.StringUtils; 9 | 10 | import java.net.URLEncoder; 11 | import java.nio.charset.StandardCharsets; 12 | import java.text.DecimalFormat; 13 | import java.text.NumberFormat; 14 | 15 | abstract class AbstractSearchComponentImpl implements SearchComponent { 16 | 17 | protected static final String DOCUMENTATION_PATH = "/documentation"; 18 | protected static final String DIAGRAMS_PATH = "/diagrams"; 19 | protected static final String DECISIONS_PATH = "/decisions"; 20 | 21 | protected static final String MARKDOWN_SECTION_HEADING = "## "; 22 | protected static final String ASCIIDOC_SECTION_HEADING = "== "; 23 | protected static final String NEWLINE = "\n"; 24 | 25 | protected String urlEncode(String value) throws Exception { 26 | return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); 27 | } 28 | 29 | protected String calculateUrlForSection(Element element, int sectionNumber) throws Exception { 30 | String url = ""; 31 | if (element instanceof Component) { 32 | url = "/" + urlEncode(element.getParent().getParent().getName()) + "/" + urlEncode(element.getParent().getName()) + "/" + urlEncode(element.getName()); 33 | } else if (element instanceof Container) { 34 | url = "/" + urlEncode(element.getParent().getName()) + "/" + urlEncode(element.getName()); 35 | } else if (element instanceof SoftwareSystem) { 36 | url = "/" + urlEncode(element.getName()); 37 | } 38 | 39 | if (sectionNumber > 0) { 40 | url = url + "#" + sectionNumber; 41 | } 42 | 43 | return url; 44 | } 45 | 46 | protected String calculateUrlForDecision(Element element, Decision decision) throws Exception { 47 | String url = ""; 48 | if (element instanceof Component) { 49 | url = "/" + urlEncode(element.getParent().getParent().getName()) + "/" + urlEncode(element.getParent().getName()) + "/" + urlEncode(element.getName()); 50 | } else if (element instanceof Container) { 51 | url = "/" + urlEncode(element.getParent().getName()) + "/" + urlEncode(element.getName()); 52 | } else if (element instanceof SoftwareSystem) { 53 | url = "/" + urlEncode(element.getName()); 54 | } 55 | 56 | url = url + "#" + decision.getId(); 57 | 58 | return url; 59 | } 60 | 61 | protected String toString(long workspaceId) { 62 | // 1 -> 0000000000000001 ... this is done so that we can search for specific IDs, rather than all including '1' 63 | NumberFormat format = new DecimalFormat("0000000000000000"); 64 | return format.format(workspaceId); 65 | } 66 | 67 | protected String toString(String s) { 68 | if (StringUtils.isNullOrEmpty(s)) { 69 | return ""; 70 | } else { 71 | return s; 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/test/java/com/structurizr/onpremises/configuration/RedisConfigurerTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.configuration; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Properties; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class RedisConfigurerTests { 10 | 11 | @Test 12 | void apply_WithDefaults() { 13 | Properties properties = new Properties(); 14 | new RedisConfigurer(properties).apply(); 15 | 16 | assertEquals("redis", properties.getProperty(StructurizrProperties.REDIS_PROTOCOL)); 17 | assertEquals("localhost", properties.getProperty(StructurizrProperties.REDIS_HOST)); 18 | assertEquals("6379", properties.getProperty(StructurizrProperties.REDIS_PORT)); 19 | assertEquals("0", properties.getProperty(StructurizrProperties.REDIS_DATABASE)); 20 | assertEquals("", properties.getProperty(StructurizrProperties.REDIS_PASSWORD)); 21 | assertEquals("redis://localhost:6379", properties.getProperty(StructurizrProperties.REDIS_ENDPOINT)); 22 | assertEquals("false", properties.getProperty(StructurizrProperties.REDIS_SSL)); 23 | } 24 | 25 | @Test 26 | void apply_WithoutEndpoint() { 27 | Properties properties = new Properties(); 28 | properties.setProperty(StructurizrProperties.REDIS_HOST, "example.com"); 29 | properties.setProperty(StructurizrProperties.REDIS_PORT, "1234"); 30 | properties.setProperty(StructurizrProperties.REDIS_DATABASE, "1"); 31 | properties.setProperty(StructurizrProperties.REDIS_PASSWORD, "password"); 32 | new RedisConfigurer(properties).apply(); 33 | 34 | assertEquals("redis", properties.getProperty(StructurizrProperties.REDIS_PROTOCOL)); 35 | assertEquals("example.com", properties.getProperty(StructurizrProperties.REDIS_HOST)); 36 | assertEquals("1234", properties.getProperty(StructurizrProperties.REDIS_PORT)); 37 | assertEquals("1", properties.getProperty(StructurizrProperties.REDIS_DATABASE)); 38 | assertEquals("password", properties.getProperty(StructurizrProperties.REDIS_PASSWORD)); 39 | assertEquals("redis://example.com:1234", properties.getProperty(StructurizrProperties.REDIS_ENDPOINT)); 40 | assertEquals("false", properties.getProperty(StructurizrProperties.REDIS_SSL)); 41 | } 42 | 43 | @Test 44 | void apply_WithEndpoint() { 45 | Properties properties = new Properties(); 46 | properties.setProperty(StructurizrProperties.REDIS_ENDPOINT, "rediss://example.com:1234"); 47 | new RedisConfigurer(properties).apply(); 48 | 49 | assertEquals("rediss", properties.getProperty(StructurizrProperties.REDIS_PROTOCOL)); 50 | assertEquals("example.com", properties.getProperty(StructurizrProperties.REDIS_HOST)); 51 | assertEquals("1234", properties.getProperty(StructurizrProperties.REDIS_PORT)); 52 | assertEquals("0", properties.getProperty(StructurizrProperties.REDIS_DATABASE)); 53 | assertEquals("", properties.getProperty(StructurizrProperties.REDIS_PASSWORD)); 54 | assertEquals("rediss://example.com:1234", properties.getProperty(StructurizrProperties.REDIS_ENDPOINT)); 55 | assertEquals("true", properties.getProperty(StructurizrProperties.REDIS_SSL)); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /structurizr-onpremises/src/main/java/com/structurizr/onpremises/web/workspace/images/ImagesController.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.onpremises.web.workspace.images; 2 | 3 | import com.structurizr.onpremises.component.workspace.WorkspaceMetaData; 4 | import com.structurizr.onpremises.domain.Image; 5 | import com.structurizr.onpremises.web.workspace.AbstractWorkspaceController; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.ModelMap; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | 13 | import java.util.Comparator; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | @Controller 18 | public class ImagesController extends AbstractWorkspaceController { 19 | 20 | private static final String VIEW = "images"; 21 | 22 | @RequestMapping(value = "/workspace/{workspaceId}/images", method = RequestMethod.GET) 23 | @PreAuthorize("isAuthenticated()") 24 | public String showAuthenticatedImages( 25 | @PathVariable("workspaceId") long workspaceId, 26 | ModelMap model 27 | ) { 28 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 29 | if (workspaceMetaData == null) { 30 | return show404Page(model); 31 | } 32 | 33 | if (userCanAccessWorkspace(workspaceMetaData)) { 34 | List images = workspaceComponent.getImages(workspaceId); 35 | for (Image image : images) { 36 | if (workspaceMetaData.isOpen()) { 37 | image.setUrl("/share/" + workspaceId + "/images/" + image.getName()); 38 | } else if (workspaceMetaData.isShareable()) { 39 | image.setUrl("/share/" + workspaceId + "/" + workspaceMetaData.getSharingToken() + "/images/" + image.getName()); 40 | } 41 | } 42 | 43 | images = images.stream().filter(i -> !i.getName().endsWith("thumbnail.png") && !i.getName().endsWith("thumbnail-dark.png")).collect(Collectors.toList()); 44 | images.sort(Comparator.comparing(i -> i.getName().toLowerCase())); 45 | 46 | model.addAttribute("images", images); 47 | 48 | boolean editable = workspaceMetaData.hasNoUsersConfigured() || workspaceMetaData.isWriteUser(getUser()); 49 | 50 | return showAuthenticatedView(VIEW, workspaceMetaData, null, null, model, true, editable); 51 | } 52 | 53 | return show404Page(model); 54 | } 55 | 56 | @RequestMapping(value = "/workspace/{workspaceId}/images/delete", method = RequestMethod.POST) 57 | public String deletePublishedImages(@PathVariable("workspaceId") long workspaceId, ModelMap model) { 58 | WorkspaceMetaData workspaceMetaData = workspaceComponent.getWorkspaceMetaData(workspaceId); 59 | if (workspaceMetaData == null) { 60 | return show404Page(model); 61 | } 62 | if (workspaceMetaData.hasNoUsersConfigured() || workspaceMetaData.isWriteUser(getUser())) { 63 | workspaceComponent.deleteImages(workspaceId); 64 | } 65 | 66 | return "redirect:/workspace/" + workspaceId + "/images"; 67 | } 68 | 69 | } --------------------------------------------------------------------------------