Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/175 Template Engine for HTML UIs #176

Merged
merged 43 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0302f07
added missing constants
hhund Feb 7, 2024
e5bd6ac
thymeleaf learning test
hhund Feb 7, 2024
a72e6e2
new thymeleaf based html generator implementation
hhund Feb 7, 2024
4818a40
removed not needed instanceof pattern variables
hhund Feb 7, 2024
66ef4d4
removed not needed method parameter
hhund Feb 7, 2024
3f27503
refactored code Arrays.asList -> List.of
hhund Feb 12, 2024
d5f25f8
status code only error handler for all mime-types
hhund Feb 12, 2024
ebe39ce
modified impl to directly reject non status requests on status port
hhund Feb 12, 2024
15574ff
allow optional questionnaire items
wetret Feb 12, 2024
0c834d3
remove semicolons at end of statements
wetret Feb 12, 2024
3e233f5
Merge branch 'issue/175_Template_Engine_for_HTML_UIs' of [email protected]
hhund Feb 12, 2024
c2feae8
remove not needed imports
hhund Feb 12, 2024
1b9977a
moved adapt... calls to existing if statements in main, code formatting
hhund Feb 12, 2024
feeb991
modified word-break behavior of h1 page title
hhund Feb 12, 2024
d813f9b
use expectedForbidden() or expectedBadRequest() instead of try-catch
wetret Feb 12, 2024
4b8c8fd
form.css and form.js only for QuestionnaireResponse and Task resources
hhund Feb 12, 2024
c17e8c8
Merge branch 'issue/175_Template_Engine_for_HTML_UIs' of [email protected]
hhund Feb 12, 2024
5b306c1
load html-tab as well if url ends with /
wetret Feb 12, 2024
f52d898
code formatting
hhund Feb 12, 2024
ef542cb
removed not needed escape chars, added support for trailing / chars
hhund Feb 12, 2024
f5d1962
improving Task.instantiates-canonical & ActivityDefinition.url patterns
hhund Feb 12, 2024
eea7abc
removed not needed escape chars, remote property injection defense
hhund Feb 12, 2024
c139df4
added TODO comments
hhund Feb 12, 2024
8ef8392
ui mod and theme feature, conf prop description changes, code formatting
hhund Feb 13, 2024
9e378a0
remove static bpe config file
hhund Feb 14, 2024
6a3d5fc
initial BPE UI, new common code, bpe rev proxy, test setups mods
hhund Feb 14, 2024
4a98aeb
margin fixes
hhund Feb 18, 2024
a9753cc
removed not needed line breaks and spaces from html
hhund Feb 18, 2024
e01549a
integrated bpmn.io viewer
hhund Feb 18, 2024
9918a8f
removed not needed RequestContextListener from servlet context
hhund Feb 18, 2024
50fd7c8
Merge remote-tracking branch 'origin/develop' into
hhund Feb 19, 2024
0146506
no more empty items in QuestionnaireResponse if items optional
hhund Feb 19, 2024
288e5e2
added default value for `dev.dsf.bpe.server.base.url` config parameter
hhund Feb 19, 2024
54487f4
improved code readability
hhund Feb 19, 2024
3a186a9
improved warn log message
hhund Feb 19, 2024
3ee24ab
removed not needed attribute, bpmn stored as data-url in download a-tag
hhund Feb 19, 2024
7e22ee5
formatting of RUN command aligned with bpe Dockerfile RUN command
hhund Feb 19, 2024
420a3cb
added missing empty ui folder to bpe docker image
hhund Feb 19, 2024
d26e31a
thymeleafContexts list entry sort order
hhund Feb 19, 2024
64b6a92
removed redundant mod.css files, now using fhir/bpe specific mod files
hhund Feb 19, 2024
36edc1e
removed static mod.css, mod.css only included in html if exists
hhund Feb 19, 2024
9f2dae0
removed not needed log message placeholders
hhund Feb 19, 2024
6ccd70d
simplified logic statement
hhund Feb 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
###
# dsf-bpe ignores
###
dsf-bpe/dsf-bpe-server-jetty/conf/config.properties
dsf-bpe/dsf-bpe-server-jetty/docker/dsf_bpe.jar
dsf-bpe/dsf-bpe-server-jetty/docker/dsf_status_client.jar
dsf-bpe/dsf-bpe-server-jetty/docker/lib/*.jar
dsf-bpe/dsf-bpe-server-jetty/docker/lib_external/*.jar
dsf-bpe/dsf-bpe-server-jetty/process/*.jar
dsf-bpe/dsf-bpe-server-jetty/ui

###
# dsf-fhir ignores
Expand All @@ -26,6 +28,7 @@ dsf-fhir/dsf-fhir-server-jetty/conf/config.properties
dsf-fhir/dsf-fhir-server-jetty/docker/dsf_fhir.jar
dsf-fhir/dsf-fhir-server-jetty/docker/dsf_status_client.jar
dsf-fhir/dsf-fhir-server-jetty/docker/lib/*.jar
dsf-fhir/dsf-fhir-server-jetty/ui
dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/bundle.xml

###
Expand All @@ -36,6 +39,7 @@ dsf-docker-test-setup/bpe/log/*.log.gz
dsf-docker-test-setup/bpe/lib_external/*.jar
dsf-docker-test-setup/bpe/process/*.jar
dsf-docker-test-setup/bpe/secrets/*.pem
dsf-docker-test-setup/bpe/.env

dsf-docker-test-setup/fhir/log/*.log
dsf-docker-test-setup/fhir/log/*.log.gz
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dev.dsf.bpe.config;

import java.util.Arrays;
import java.util.List;

import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer;
Expand All @@ -20,6 +19,6 @@ protected String mavenServerModuleName()
@Override
protected List<Class<? extends ServletContainerInitializer>> servletContainerInitializers()
{
return Arrays.asList(JerseyServletContainerInitializer.class, SpringServletContainerInitializer.class);
return List.of(JerseyServletContainerInitializer.class, SpringServletContainerInitializer.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dev.dsf.bpe.config;

import java.util.Arrays;
import java.util.List;

import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer;
Expand All @@ -20,6 +19,6 @@ protected String mavenServerModuleName()
@Override
protected List<Class<? extends ServletContainerInitializer>> servletContainerInitializers()
{
return Arrays.asList(JerseyServletContainerInitializer.class, SpringServletContainerInitializer.class);
return List.of(JerseyServletContainerInitializer.class, SpringServletContainerInitializer.class);
}
}
4 changes: 4 additions & 0 deletions dsf-bpe/dsf-bpe-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
<groupId>dev.dsf</groupId>
<artifactId>dsf-common-status</artifactId>
</dependency>
<dependency>
<groupId>dev.dsf</groupId>
<artifactId>dsf-common-ui</artifactId>
</dependency>
<dependency>
<groupId>de.hs-heilbronn.mi</groupId>
<artifactId>crypto-utils</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package dev.dsf.bpe;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import dev.dsf.common.auth.filter.AuthenticationFilter;
import jakarta.inject.Inject;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.ApplicationPath;
Expand Down Expand Up @@ -36,5 +38,8 @@ public BpeJerseyApplication(ServletContext servletContext)

register(b);
});

register(AuthenticationFilter.class);
register(RolesAllowedDynamicFeature.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package dev.dsf.bpe.authentication;

import java.util.stream.Stream;

import dev.dsf.common.auth.conf.DsfRole;

public enum BpeServerRole implements DsfRole
{
ORGANIZATION
ADMIN;

public static boolean isValid(String role)
{
return role != null && !role.isBlank() && Stream.of(values()).map(Enum::name).anyMatch(n -> n.equals(role));
}
}
Original file line number Diff line number Diff line change
@@ -1,150 +1,73 @@
package dev.dsf.bpe.authentication;

import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import javax.security.auth.x500.X500Principal;

import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Practitioner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import dev.dsf.common.auth.DsfOpenIdCredentials;
import dev.dsf.common.auth.conf.DsfRole;
import dev.dsf.bpe.service.LocalOrganizationProvider;
import dev.dsf.common.auth.conf.AbstractIdentityProvider;
import dev.dsf.common.auth.conf.Identity;
import dev.dsf.common.auth.conf.IdentityProvider;
import dev.dsf.common.auth.conf.OrganizationIdentity;
import dev.dsf.common.auth.conf.PractitionerIdentity;
import dev.dsf.common.auth.conf.PractitionerIdentityImpl;
import dev.dsf.common.auth.conf.RoleConfig;

public class IdentityProviderImpl implements IdentityProvider
public class IdentityProviderImpl extends AbstractIdentityProvider implements IdentityProvider, InitializingBean
{
@Override
public Identity getIdentity(DsfOpenIdCredentials credentials)
{
return new PractitionerIdentity()
{
@Override
public String getName()
{
return credentials.getUserId();
}

@Override
public String getDisplayName()
{
return getName();
}

@Override
public boolean isLocalIdentity()
{
return true;
}
private static final Logger logger = LoggerFactory.getLogger(IdentityProviderImpl.class);

@Override
public boolean hasDsfRole(DsfRole role)
{
return BpeServerRole.ORGANIZATION.equals(role);
}
private final LocalOrganizationProvider organizationProvider;

@Override
public Set<DsfRole> getDsfRoles()
{
return Collections.singleton(BpeServerRole.ORGANIZATION);
}

@Override
public Optional<String> getOrganizationIdentifierValue()
{
return Optional.empty();
}

@Override
public Organization getOrganization()
{
return null;
}
public IdentityProviderImpl(RoleConfig roleConfig, LocalOrganizationProvider organizationProvider)
{
super(roleConfig);

@Override
public Practitioner getPractitioner()
{
return null;
}
this.organizationProvider = organizationProvider;
}

@Override
public Set<Coding> getPractionerRoles()
{
return Collections.emptySet();
}
@Override
public void afterPropertiesSet() throws Exception
{
super.afterPropertiesSet();

@Override
public Optional<DsfOpenIdCredentials> getCredentials()
{
return Optional.of(credentials);
}
Objects.requireNonNull(organizationProvider, "organizationProvider");
}

@Override
public Optional<X509Certificate> getCertificate()
{
return Optional.empty();
}
};
@Override
protected Optional<Organization> getLocalOrganization()
{
return organizationProvider.getLocalOrganization();
}

@Override
public Identity getIdentity(X509Certificate[] certificates)
{
return new OrganizationIdentity()
{
@Override
public String getName()
{
return certificates[0].getSubjectX500Principal().getName(X500Principal.RFC1779);
}

@Override
public String getDisplayName()
{
return getName();
}
if (certificates == null || certificates.length == 0)
return null;

@Override
public Set<DsfRole> getDsfRoles()
{
return Collections.singleton(BpeServerRole.ORGANIZATION);
}
String thumbprint = getThumbprint(certificates[0]);

@Override
public Organization getOrganization()
{
return null;
}

@Override
public boolean isLocalIdentity()
{
return true;
}

@Override
public Optional<String> getOrganizationIdentifierValue()
{
return Optional.empty();
}

@Override
public boolean hasDsfRole(DsfRole role)
{
return BpeServerRole.ORGANIZATION.equals(role);
}
Optional<Practitioner> practitioner = toPractitioner(certificates[0]);
Optional<Organization> localOrganization = organizationProvider.getLocalOrganization();
if (practitioner.isPresent() && localOrganization.isPresent())
{
Practitioner p = practitioner.get();
Organization o = localOrganization.get();

@Override
public Optional<X509Certificate> getCertificate()
{
return Optional.of(certificates[0]);
}
};
return new PractitionerIdentityImpl(o, getDsfRolesFor(p, thumbprint, null, null), certificates[0], p,
getPractitionerRolesFor(p, thumbprint, null, null), null);
}
else
{
logger.warn(
"Certificate with thumbprint '{}' for '{}' unknown, not part of allowlist and not configured as local user or local organization",
thumbprint, getDn(certificates[0]));
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ Resource getResource()
.compile(Pattern.quote(PLACEHOLDER_PREFIX_TMP));
private static final Pattern PLACEHOLDER_PREFIX_PATTERN = Pattern.compile(Pattern.quote(PLACEHOLDER_PREFIX));

private static final String ACTIVITY_DEFINITION_URL_PATTERN_STRING = "^(?<processUrl>http[s]{0,1}://(?<domain>(?:(?:[a-zA-Z0-9]{1,63}|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])\\.)+(?:[a-zA-Z0-9]{1,63}))"
private static final String ACTIVITY_DEFINITION_URL_PATTERN_STRING = "^(?<processUrl>http[s]{0,1}://(?<domain>(?:(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])\\.)+(?:[a-zA-Z0-9]{1,63}))"
+ "/bpe/Process/(?<processName>[a-zA-Z0-9-]+))$";
private static final Pattern ACTIVITY_DEFINITION_URL_PATTERN = Pattern
.compile(ACTIVITY_DEFINITION_URL_PATTERN_STRING);

private static final String INSTANTIATES_CANONICAL_PATTERN_STRING = "(?<processUrl>http[s]{0,1}://(?<domain>(?:(?:[a-zA-Z0-9]{1,63}|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])\\.)+(?:[a-zA-Z0-9]{1,63}))"
private static final String INSTANTIATES_CANONICAL_PATTERN_STRING = "(?<processUrl>http[s]{0,1}://(?<domain>(?:(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])\\.)+(?:[a-zA-Z0-9]{1,63}))"
+ "/bpe/Process/(?<processName>[a-zA-Z0-9-]+))\\|(?<processVersion>\\d+\\.\\d+)$";
private static final Pattern INSTANTIATES_CANONICAL_PATTERN = Pattern
.compile(INSTANTIATES_CANONICAL_PATTERN_STRING);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.dsf.bpe.service;

import java.util.Optional;

import org.hl7.fhir.r4.model.Organization;

public interface LocalOrganizationProvider
{
Optional<Organization> getLocalOrganization();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dev.dsf.bpe.service;

import java.time.LocalDateTime;
import java.time.temporal.TemporalAmount;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import org.hl7.fhir.r4.model.Organization;
import org.springframework.beans.factory.InitializingBean;

import dev.dsf.bpe.v1.service.OrganizationProvider;

public class LocalOrganizationProviderImpl implements LocalOrganizationProvider, InitializingBean
{
private record OrganizationEntry(Optional<Organization> organization, LocalDateTime readTime)
{
}

private final AtomicReference<OrganizationEntry> organization = new AtomicReference<>();

private final TemporalAmount cacheTimeout;
private final OrganizationProvider delegate;

public LocalOrganizationProviderImpl(TemporalAmount cacheTimeout, OrganizationProvider delegate)
{
this.cacheTimeout = cacheTimeout;
this.delegate = delegate;
}

@Override
public void afterPropertiesSet() throws Exception
{
Objects.requireNonNull(cacheTimeout, "cacheTimeout");
Objects.requireNonNull(delegate, "delegate");
}

@Override
public Optional<Organization> getLocalOrganization()
{
OrganizationEntry entry = organization.get();
if (entry == null || entry.organization().isEmpty()
|| LocalDateTime.now().isAfter(entry.readTime().plus(cacheTimeout)))
{
Optional<Organization> o = delegate.getLocalOrganization();
if (organization.compareAndSet(entry, new OrganizationEntry(o, LocalDateTime.now())))
return o;
else
return organization.get().organization();
}
else
return entry.organization();
}
}
Loading
Loading