Skip to content

Commit

Permalink
browser security policy headers for text/html and static content urls
Browse files Browse the repository at this point in the history
Removes in-line style and event handlers not compatible with header
security policies.
All event handlers now registered via main.js window event-handler on
'DOMContentLoaded'.
  • Loading branch information
hhund committed Oct 16, 2023
1 parent 4b6bc21 commit a208bed
Show file tree
Hide file tree
Showing 13 changed files with 622 additions and 494 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.Task;
import org.springframework.web.util.HtmlUtils;

import ca.uhn.fhir.context.FhirContext;
Expand Down Expand Up @@ -159,6 +158,7 @@ public void writeTo(Resource resource, Class<?> type, Type genericType, Annotati
<link rel="icon" type="image/png" href="static/favicon_32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="static/favicon_96x96.png" sizes="96x96">
<meta name="theme-color" content="#326F95">
<script src="static/main.js"></script>
<script src="static/util.js"></script>
<script src="static/prettify.js"></script>
<script src="static/tabs.js"></script>
Expand All @@ -171,8 +171,7 @@ public void writeTo(Resource resource, Class<?> type, Type genericType, Annotati
""".replace("${serverBaseUrl}", getServerBaseUrlPathWithLeadingSlash()));
out.write("<title>" + getTitle() + "</title>\n");
out.write("</head>\n");
out.write("<body onload=\"prettyPrint();openInitialTab(" + String.valueOf(htmlEnabled) + ");checkBookmarked();"
+ adaptFormInputsIfTask(resource) + "setUiTheme();\">\n");
out.write("<body>\n");

out.write("<div id=\"icons\">\n");

Expand Down Expand Up @@ -200,46 +199,46 @@ public void writeTo(Resource resource, Class<?> type, Type genericType, Annotati

out.write(
"""
<svg class="icon" id="help-icon" viewBox="0 0 24 24" onclick="showHelp();">
<svg class="icon" id="help-icon" viewBox="0 0 24 24">
<title>Show Help</title>
<path d="M11.07,12.85c0.77-1.39,2.25-2.21,3.11-3.44c0.91-1.29,0.4-3.7-2.18-3.7c-1.69,0-2.52,1.28-2.87,2.34L6.54,6.96 C7.25,4.83,9.18,3,11.99,3c2.35,0,3.96,1.07,4.78,2.41c0.7,1.15,1.11,3.3,0.03,4.9c-1.2,1.77-2.35,2.31-2.97,3.45 c-0.25,0.46-0.35,0.76-0.35,2.24h-2.89C10.58,15.22,10.46,13.95,11.07,12.85z M14,20c0,1.1-0.9,2-2,2s-2-0.9-2-2c0-1.1,0.9-2,2-2 S14,18.9,14,20z"/>
</svg>
<svg class="icon" id="light-mode" height="24" viewBox="0 -960 960 960" width="24" onclick="setUiTheme('light');">
<svg class="icon" id="light-mode" height="24" viewBox="0 -960 960 960" width="24">
<title>Enable Light Mode</title>
<path d="M479.765-340Q538-340 579-380.765q41-40.764 41-99Q620-538 579.235-579q-40.764-41-99-41Q422-620 381-579.235q-41 40.764-41 99Q340-422 380.765-381q40.764 41 99 41Zm.235 60q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM70-450q-12.75 0-21.375-8.675Q40-467.351 40-480.175 40-493 48.625-501.5T70-510h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T170-450H70Zm720 0q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T790-510h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T890-450H790ZM479.825-760Q467-760 458.5-768.625T450-790v-100q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510-890v100q0 12.75-8.675 21.375-8.676 8.625-21.5 8.625Zm0 720Q467-40 458.5-48.625T450-70v-100q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510-170v100q0 12.75-8.675 21.375Q492.649-40 479.825-40ZM240-678l-57-56q-9-9-8.629-21.603.37-12.604 8.526-21.5 8.896-8.897 21.5-8.897Q217-786 226-777l56 57q8 9 8 21t-8 20.5q-8 8.5-20.5 8.5t-21.5-8Zm494 495-56-57q-8-9-8-21.375T678.5-282q8.5-9 20.5-9t21 9l57 56q9 9 8.629 21.603-.37 12.604-8.526 21.5-8.896 8.897-21.5 8.897Q743-174 734-183Zm-56-495q-9-9-9-21t9-21l56-57q9-9 21.603-8.629 12.604.37 21.5 8.526 8.897 8.896 8.897 21.5Q786-743 777-734l-57 56q-8 8-20.364 8-12.363 0-21.636-8ZM182.897-182.897q-8.897-8.896-8.897-21.5Q174-217 183-226l57-56q8.8-9 20.9-9 12.1 0 20.709 9Q291-273 291-261t-9 21l-56 57q-9 9-21.603 8.629-12.604-.37-21.5-8.526ZM480-480Z"/>
</svg>
<svg class="icon" id="dark-mode" height="24" viewBox="0 -960 960 960" width="24" onclick="setUiTheme('dark');">
<svg class="icon" id="dark-mode" height="24" viewBox="0 -960 960 960" width="24">
<title>Enable Dark Mode</title>
<path d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q8 0 17 .5t23 1.5q-36 32-56 79t-20 99q0 90 63 153t153 63q52 0 99-18.5t79-51.5q1 12 1.5 19.5t.5 14.5q0 150-105 255T480-120Zm0-60q109 0 190-67.5T771-406q-25 11-53.667 16.5Q688.667-384 660-384q-114.689 0-195.345-80.655Q384-545.311 384-660q0-24 5-51.5t18-62.5q-98 27-162.5 109.5T180-480q0 125 87.5 212.5T480-180Zm-4-297Z"/>
</svg>
<a href="" download="" id="download-link" title="">
<svg class="icon" id="download" viewBox="0 0 24 24">
<path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/>
</svg></a>
<svg class="icon" id="bookmark-add" viewBox="0 0 24 24" onclick="addCurrentBookmark();">
<svg class="icon" id="bookmark-add" viewBox="0 0 24 24">
<title>Add Bookmark</title>
<path d="M17,11v6.97l-5-2.14l-5,2.14V5h6V3H7C5.9,3,5,3.9,5,5v16l7-3l7,3V11H17z M21,7h-2v2h-2V7h-2V5h2V3h2v2h2V7z"/>
</svg>
<svg class="icon" id="bookmark-remove" viewBox="0 0 24 24" onclick="removeCurrentBookmark();" style="display:none;">
<svg class="icon" id="bookmark-remove" viewBox="0 0 24 24">
<title>Remove Bookmark</title>
<path d="M17,11v6.97l-5-2.14l-5,2.14V5h6V3H7C5.9,3,5,3.9,5,5v16l7-3l7,3V11H17z M21,7h-6V5h6V7z"/>
</svg>
<svg class="icon" id="bookmark-list" viewBox="0 0 24 24" onclick="showBookmarks();">
<svg class="icon" id="bookmark-list" viewBox="0 0 24 24">
<title>Show Bookmarks</title>
<path d="M9,1H19A2,2 0 0,1 21,3V19L19,18.13V3H7A2,2 0 0,1 9,1M15,20V7H5V20L10,17.82L15,20M15,5C16.11,5 17,5.9 17,7V23L10,20L3,23V7A2,2 0 0,1 5,5H15Z"/>
</svg>
</div>
<div id="help" style="display:none;">
<div id="help">
<h3 id="help-title">Query Parameters</h3>
<svg class="icon" id="help-close" viewBox="0 0 24 24" onclick="closeHelp();">
<svg class="icon" id="help-close" viewBox="0 0 24 24">
<title>Close Help</title>
<path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
</svg>
<div id="help-list"></div>
</div>
<div id="bookmarks" style="display:none;">
<div id="bookmarks">
<h3 id="bookmarks-title">Bookmarks</h3>
<svg class="icon" id="bookmark-list-close" viewBox="0 0 24 24" onclick="closeBookmarks();">
<svg class="icon" id="bookmark-list-close" viewBox="0 0 24 24">
<title>Close Bookmarks</title>
<path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
</svg>
Expand All @@ -263,11 +262,11 @@ public void writeTo(Resource resource, Class<?> type, Type genericType, Annotati
""");

if (htmlEnabled)
out.write("<button id=\"html-button\" class=\"tablinks\" onclick=\"openTab('html')\">html</button>\n");
out.write("<button id=\"html-button\" class=\"tablinks\">html</button>\n");

out.write("""
<button id="json-button" class="tablinks" onclick="openTab('json')">json</button>
<button id="xml-button" class="tablinks" onclick="openTab('xml')">xml</button>
<button id="json-button" class="tablinks">json</button>
<button id="xml-button" class="tablinks">xml</button>
</div>
""");

Expand Down Expand Up @@ -366,7 +365,7 @@ private void writeXml(MediaType mediaType, Resource resource, OutputStreamWriter
{
IParser parser = getParser(mediaType, fhirContext::newXmlParser);

out.write("<pre id=\"xml\" class=\"prettyprint linenums lang-xml\" style=\"display:none;\">");
out.write("<pre id=\"xml\" class=\"prettyprint linenums lang-xml\">");
String content = parser.encodeResourceToString(resource);

content = content.replace("&amp;", "&amp;amp;").replace("&apos;", "&amp;apos;").replace("&gt;", "&amp;gt;")
Expand Down Expand Up @@ -427,7 +426,7 @@ private void writeJson(MediaType mediaType, Resource resource, OutputStreamWrite
{
IParser parser = getParser(mediaType, fhirContext::newJsonParser);

out.write("<pre id=\"json\" class=\"prettyprint linenums lang-json\" style=\"display:none;\">");
out.write("<pre id=\"json\" class=\"prettyprint linenums lang-json\">");
String content = parser.encodeResourceToString(resource).replace("<", "&lt;").replace(">", "&gt;");

Matcher urlMatcher = URL_PATTERN.matcher(content);
Expand All @@ -454,7 +453,7 @@ private void writeJson(MediaType mediaType, Resource resource, OutputStreamWrite
@SuppressWarnings("unchecked")
private void writeHtml(Class<?> resourceType, Resource resource, OutputStreamWriter out) throws IOException
{
out.write("<div id=\"html\" class=\"prettyprint lang-html\" style=\"display:none;\">\n");
out.write("<div id=\"html\" class=\"prettyprint lang-html\">\n");

URI resourceUri = getResourceUri(resource);

Expand All @@ -473,14 +472,6 @@ private boolean isHtmlEnabled(Class<?> resourceType, Resource resource) throws M
.anyMatch(g -> g.isResourceSupported(resourceUri, resource));
}

private String adaptFormInputsIfTask(Resource resource)
{
if (resource instanceof Task task)
return Task.TaskStatus.DRAFT.equals(task.getStatus()) ? "adaptTaskFormInputs();" : "";
else
return "";
}

private Optional<String> getResourceName(Resource resource, String uuid)
{
if (resource instanceof Bundle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,7 @@ private void writePlaceholderButton(String elementName, String value, boolean wr
{
if (writable)
{
out.write("<svg class=\"input-group-svg\" height=\"22\" width=\"22\" viewBox=\"0 -960 960 960\" "
+ "onclick=\"insertPlaceholderInValue(this.parentElement, '" + elementName + "', '" + value
+ "')\">\n");
out.write("<svg class=\"input-group-svg\" height=\"22\" width=\"22\" viewBox=\"0 -960 960 960\">\n");
out.write("<title>Use placeholder value</title>\n");
out.write(
"<path d=\"M140-160q-24 0-42-18t-18-42v-169h60v169h680v-520H140v171H80v-171q0-24 18-42t42-18h680q24 0 42 18t18 42v520q0 24-18 42t-42 18H140Zm319-143-43-43 103-103H80v-60h439L416-612l43-43 176 176-176 176Z\"/>\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void writeHtml(URI resourceUri, QuestionnaireResponse questionnaireRespon
{
out.write("<div class=\"row row-submit\" name=\"submit-row\">\n");
out.write(
"<button type=\"button\" name=\"submit\" class=\"submit\" onclick=\"completeQuestionnaireResponse();\">Submit</button>\n");
"<button id=\"complete-questionnaire-response\" type=\"button\" name=\"submit\" class=\"submit\">Submit</button>\n");
out.write("</div>\n");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void writeHtml(URI resourceUri, Bundle resource, OutputStreamWriter out)
if (previous.isPresent())
out.write("</a>");

out.write("</td><td style=\"text-align:center;vertical-align:top;\">");
out.write("</td><td>");
if (resource.getEntry().size() > 0)
{
int page = getPage(resourceUri);
Expand All @@ -142,7 +142,7 @@ public void writeHtml(URI resourceUri, Bundle resource, OutputStreamWriter out)
out.write("<span id=\"resources\">Resources " + firstResource + " - " + lastResource + " / "
+ resource.getTotal() + "</span><span id=\"page\">Page " + page + " / " + max + "</span>");
}
out.write("</td><td style=\"text-align:right;\">");
out.write("</td><td>");

Optional<String> next = resource.getLink().stream().filter(l -> "next".equals(l.getRelation())).findFirst()
.map(BundleLinkComponent::getUrl);
Expand Down Expand Up @@ -170,9 +170,7 @@ public void writeHtml(URI resourceUri, Bundle resource, OutputStreamWriter out)
.filter(e -> e.hasResource() && e.hasSearch() && e.getSearch().hasMode()
&& SearchEntryMode.MATCH.equals(e.getSearch().getMode()))
.map(BundleEntryComponent::getResource)
.map(r -> "<tr onClick=\"if(event.target?.tagName?.toLowerCase() !== 'a') window.location=document.head.baseURI + '"
+ r.getIdElement().toVersionless().getValueAsString() + "'\" title=\"Open "
+ r.getResourceType().name() + "\">" + getRow(r) + "</tr>\n")
.map(r -> "<tr title=\"Open " + r.getResourceType().name() + "\">" + getRow(r) + "</tr>\n")
.collect(Collectors.joining()));
out.write("</table></div>");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ public void writeHtml(URI resourceUri, Task task, OutputStreamWriter out) throws
if (draft)
{
out.write("<div class=\"row row-submit\" name=\"submit-row\">\n");
out.write("<button type=\"button\" name=\"submit\" class=\"submit\" " + "onclick=\"startProcess();\""
+ ">Start Process</button>\n");
out.write("<button id=\"start-process\" type=\"button\" name=\"submit\" class=\"submit\">Start Process</button>\n");
out.write("</div>\n");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import dev.dsf.common.auth.logout.LogoutService;
import dev.dsf.common.status.webservice.StatusService;
import dev.dsf.fhir.exception.DataFormatExceptionHandler;
import dev.dsf.fhir.webservice.filter.BrowserPolicyHeaderResponseFilter;
import dev.dsf.fhir.webservice.impl.ActivityDefinitionServiceImpl;
import dev.dsf.fhir.webservice.impl.BinaryServiceImpl;
import dev.dsf.fhir.webservice.impl.BundleServiceImpl;
Expand Down Expand Up @@ -160,6 +161,12 @@ public class WebserviceConfig
@Autowired
private HistoryConfig historyConfig;

@Bean
public BrowserPolicyHeaderResponseFilter browserPolicyHeaderResponseFilter()
{
return new BrowserPolicyHeaderResponseFilter();
}

@Bean
public DataFormatExceptionHandler dataFormatExceptionHandler()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.dsf.fhir.webservice.filter;

import java.io.IOException;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.Provider;

@Provider
public class BrowserPolicyHeaderResponseFilter implements ContainerResponseFilter
{
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException
{
if ((requestContext.getAcceptableMediaTypes() != null
&& requestContext.getAcceptableMediaTypes().contains(MediaType.TEXT_HTML_TYPE))
|| (requestContext.getUriInfo() != null && requestContext.getUriInfo().getPath() != null
&& requestContext.getUriInfo().getPath().startsWith("static/")))
{
MultivaluedMap<String, Object> headers = responseContext.getHeaders();

headers.add("X-Content-Type-Options", "nosniff");
headers.add("Referrer-Policy", "strict-origin-when-cross-origin");
headers.add("Cross-Origin-Opener-Policy", "same-origin");
headers.add("Cross-Origin-Embedder-Policy", "require-corp");
headers.add("Cross-Origin-Resource-Policy", "same-site");
headers.add("Permissions-Policy", "geolocation=(), camera=(), microphone=()");
headers.add("Content-Security-Policy",
"frame-ancestors 'none'; default-src 'self'; frame-src 'none'; media-src 'none'; object-src 'none'; worker-src 'none'");
}
}
}
11 changes: 6 additions & 5 deletions dsf-fhir/dsf-fhir-server/src/main/resources/static/bookmarks.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,18 @@ function createBookmarkList(bookmarks) {
}
if (e[1].length > 0) {
e[1].filter(b => b !== e[0]).forEach(b => {
const c = counter;
const div = document.createElement("div");
div.setAttribute('id', 'bookmarks-list-entry-' + counter);
div.setAttribute('id', 'bookmarks-list-entry-' + c);
const divAddIcon = addIcon.cloneNode(true);
divAddIcon.setAttribute('id', 'bookmark-add-' + counter);
divAddIcon.setAttribute('onclick', "addBookmark('" + b + "', " + counter + ")");
divAddIcon.setAttribute('id', 'bookmark-add-' + c);
divAddIcon.addEventListener('click', () => addBookmark(b, c));
divAddIcon.setAttribute('viewBox', '4 0 24 24');
divAddIcon.style.display = 'none';
divAddIcon.children[0].innerHTML = 'Add Bookmark: ' + b;
const divRemoveIcon = removeIcon.cloneNode(true);
divRemoveIcon.setAttribute('id', 'bookmark-remove-' + counter);
divRemoveIcon.setAttribute('onclick', "removeBookmark('" + b + "', " + counter + ")");
divRemoveIcon.setAttribute('id', 'bookmark-remove-' + c);
divRemoveIcon.addEventListener('click', () => removeBookmark(b, c));
divRemoveIcon.setAttribute('viewBox', '4 0 24 24');
divRemoveIcon.style.display = 'inline';
divRemoveIcon.children[0].innerHTML = 'Remove Bookmark: ' + b;
Expand Down
44 changes: 37 additions & 7 deletions dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,21 @@ pre {
margin-top: 0;
}

pre.lang-xml {
display: none;
}

pre.lang-json {
display: none;
}

pre.lang-html {
display: none;
border: 0;
font-family: monospace;
}


#icons {
position: absolute;
top: 2em;
Expand Down Expand Up @@ -190,7 +200,12 @@ pre.lang-html {
fill: #aaa;
}

#bookmark-remove.icon {
display: none;
}

#help {
display: none;
position: absolute;
top: 1em;
right: 1em;
Expand Down Expand Up @@ -220,6 +235,11 @@ pre.lang-html {
.help-param {
font-size: small;
font-family: sans-serif;
margin-bottom: 1.2em;
}

.help-param:last-child {
margin-bottom: 0;
}

.help-param-name {
Expand All @@ -234,10 +254,11 @@ pre.lang-html {
.help-param-documentation {
max-width: 50em;
margin-top: 0.5em;
margin-bottom: 1.2em;
margin-bottom: 0;
}

#bookmarks {
display: none;
position: absolute;
top: 1em;
right: 1em;
Expand Down Expand Up @@ -304,10 +325,25 @@ pre.lang-html {
color: #aaa !important;
}

.bundle {
border: 1px solid #ccc;
padding: 20px 20px 10px 20px;
color: var(--color-prime);
}

.bundle>#header>table {
width: 100%
}

.bundle>#header>table td:nth-child(2) {
text-align: center;
vertical-align: top;
}

.bundle>#header>table td:nth-child(3) {
text-align: right;
}

.bundle>#header #resources {
padding-right: 1em;
color: #aaa
Expand All @@ -318,12 +354,6 @@ pre.lang-html {
color: #aaa
}

.bundle {
border: 1px solid #ccc;
padding: 20px 20px 10px 20px;
color: var(--color-prime);
}

.bundle>#list {
overflow-x: auto;
}
Expand Down
Loading

0 comments on commit a208bed

Please sign in to comment.