Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
76e5337
Admin UI for adding external hosts to a variety of directives
labkey-adam Feb 21, 2025
83aa1cb
Reduce per-request work for CSP filter to a single substitution. Impr…
labkey-adam Feb 21, 2025
32d43fa
Checkpoint - persistence, initialization, and add new hosts
labkey-adam Feb 22, 2025
b4802e3
Checkpoint - list existing hosts, upgrade code
labkey-adam Feb 22, 2025
41519f3
Checkpoint - delete
labkey-adam Feb 22, 2025
91fdc75
Checkpoint - edit
labkey-adam Feb 22, 2025
627d94e
Substitution -> AllowedHost
labkey-adam Feb 22, 2025
a10d0b9
Upgrade code to migrate old properties
labkey-adam Feb 22, 2025
212c71d
Startup properties
labkey-adam Feb 22, 2025
06ca71a
Report CSP status (enforce CSP & expected substitutions in place). Ha…
labkey-adam Feb 24, 2025
49e0a0c
Turn CSP_FILTERS into a Map. Attempt to determine cspVersion of each …
labkey-adam Feb 24, 2025
4d21dd2
Merge remote-tracking branch 'origin/develop' into fb_admin_csp_sources
labkey-adam Feb 24, 2025
6e09508
Update comment
labkey-adam Feb 24, 2025
5b1f3d8
Clarify ambiguous name to CaseInsensitiveKeyedHashSetValuedMap. Add n…
labkey-adam Feb 24, 2025
5ad6d32
When adding new hosts, persist the most recent directive in the drop-…
labkey-adam Feb 24, 2025
42bc5c1
Merge remote-tracking branch 'origin/develop' into fb_admin_csp_sources
labkey-adam Feb 24, 2025
4ed5703
Add the actual policy (substituted and not) to metrics
labkey-adam Feb 25, 2025
7d270ee
Behold the super sophisticated CSP parser
labkey-adam Feb 25, 2025
7e69abc
No semicolons
labkey-adam Feb 26, 2025
ce04d6c
App admins can edit allowed external resource hosts, allowed redirect…
labkey-adam Feb 26, 2025
cfdd51d
Validate incoming startup properties as well
labkey-adam Feb 26, 2025
ece47c7
Merge remote-tracking branch 'origin/develop' into fb_admin_csp_sources
labkey-adam Feb 26, 2025
9036e36
Update core/src/org/labkey/core/security/AllowedExternalResourceHosts…
labkey-adam Feb 26, 2025
446e8ee
Update core/src/org/labkey/core/admin/AdminController.java
labkey-adam Feb 26, 2025
a72fba1
Core review feedback
labkey-adam Feb 26, 2025
17a6a1d
Link to CSP configuration docs page
labkey-adam Feb 27, 2025
bc6a058
Merge remote-tracking branch 'origin/develop' into fb_admin_csp_sources
labkey-adam Feb 27, 2025
239d01f
Merge remote-tracking branch 'origin/develop' into fb_admin_csp_sources
labkey-adam Feb 27, 2025
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
1 change: 1 addition & 0 deletions api/src/org/labkey/api/ApiModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ protected void doStartup(ModuleContext moduleContext)
{
SystemMaintenance.addTask(new ApiKeyMaintenanceTask());
AuthenticationManager.registerMetricsProvider();
ContentSecurityPolicyFilter.registerMetricsProvider();
ApiKeyManager.get().handleStartupProperties();
MailHelper.init();
// Handle optional feature and system maintenance startup properties as late as possible; we want all optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@

import org.apache.commons.collections4.multimap.AbstractSetValuedMap;

import java.util.HashSet;
import java.util.HashMap;
import java.util.Set;

/**
* A case-insensitive version of org.apache.commons.collections4.multimap.HashSetValuedHashMap
* An org.apache.commons.collections4.multimap.HashSetValuedHashMap with case-insensitive String values
*/
public class CaseInsensitiveHashSetValuedMap<V> extends AbstractSetValuedMap<String, V> implements CaseInsensitiveCollection
public class CaseInsensitiveHashSetValuedMap<K> extends AbstractSetValuedMap<K, String> implements CaseInsensitiveCollection
{
public CaseInsensitiveHashSetValuedMap()
{
super(new CaseInsensitiveHashMap<>());
super(new HashMap<>());
}

@Override
protected Set<V> createCollection()
protected Set<String> createCollection()
{
return new HashSet<>();
return new CaseInsensitiveHashSet();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2016 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.labkey.api.collections;

import org.apache.commons.collections4.multimap.AbstractSetValuedMap;

import java.util.HashSet;
import java.util.Set;

/**
* An org.apache.commons.collections4.multimap.HashSetValuedHashMap with case-insensitive String keys
*/
public class CaseInsensitiveKeyedHashSetValuedMap<V> extends AbstractSetValuedMap<String, V> implements CaseInsensitiveCollection
{
public CaseInsensitiveKeyedHashSetValuedMap()
{
super(new CaseInsensitiveHashMap<>());
}

@Override
protected Set<V> createCollection()
{
return new HashSet<>();
}
}
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/module/ModuleLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.labkey.api.action.UrlProviderService;
import org.labkey.api.collections.CaseInsensitiveHashMap;
import org.labkey.api.collections.CaseInsensitiveHashSet;
import org.labkey.api.collections.CaseInsensitiveHashSetValuedMap;
import org.labkey.api.collections.CaseInsensitiveKeyedHashSetValuedMap;
import org.labkey.api.collections.CaseInsensitiveTreeMap;
import org.labkey.api.collections.CaseInsensitiveTreeSet;
import org.labkey.api.collections.CopyOnWriteHashMap;
Expand Down Expand Up @@ -229,7 +229,7 @@ public enum ModuleState

// Allow multiple StartupPropertyHandlers with the same scope as long as the StartupProperty impl class is different.
private final Set<StartupPropertyHandler<? extends StartupProperty>> _startupPropertyHandlers = new ConcurrentSkipListSet<>(Comparator.comparing((StartupPropertyHandler<?> sph)->sph.getScope(), String.CASE_INSENSITIVE_ORDER).thenComparing(StartupPropertyHandler::getStartupPropertyClassName));
private final MultiValuedMap<String, StartupPropertyEntry> _startupPropertyMap = new CaseInsensitiveHashSetValuedMap<>();
private final MultiValuedMap<String, StartupPropertyEntry> _startupPropertyMap = new CaseInsensitiveKeyedHashSetValuedMap<>();

private ModuleLoader()
{
Expand Down
25 changes: 18 additions & 7 deletions api/src/org/labkey/api/security/Directive.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package org.labkey.api.security;

import org.labkey.api.settings.StartupProperty;
import org.labkey.api.util.SafeToRenderEnum;

/**
* All CSP directives that support substitutions. These constant names are persisted to the database, so be careful with
* any changes. If adding a Directive, make sure to add the corresponding substitutions to application.properties.
*/
public enum Directive
public enum Directive implements StartupProperty, SafeToRenderEnum
{
Connection("connect-src"),
Font("font-src"),
Frame("frame-src"),
Image("image-src"),
Style("style-src");
Connection("connect-src", "Sources for fetch/XHR requests"),
Font("font-src", "Sources for fonts"),
Frame("frame-src", "Sources for iframes"),
Image("image-src", "Sources for images"),
Style("style-src", "Sources for stylesheets");

private final String _cspDirective;
private final String _description;

Directive(String cspDirective)
Directive(String cspDirective, String description)
{
_cspDirective = cspDirective;
_description = description;
}

public String getCspDirective()
Expand All @@ -28,4 +33,10 @@ public String getSubstitutionKey()
{
return name().toUpperCase() + ".SOURCES";
}

@Override
public String getDescription()
{
return _description + " (" + _cspDirective + "). Multiple hosts, separated by a space, can be provided.";
}
}
6 changes: 4 additions & 2 deletions api/src/org/labkey/api/settings/AppProps.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public interface AppProps
String EXPERIMENTAL_BLOCKER = "blockMaliciousClients";
String EXPERIMENTAL_RESOLVE_PROPERTY_URI_COLUMNS = "resolve-property-uri-columns";
String DEPRECATED_OBJECT_LEVEL_DISCUSSIONS = "deprecatedObjectLevelDiscussions";
String ALLOWED_EXTERNAL_RESOURCES = "allowedExternalResources";

String UNKNOWN_VERSION = "Unknown Release Version";

Expand Down Expand Up @@ -237,16 +238,15 @@ static WriteableAppProps getWriteableInstance()
boolean isIncludeServerHttpHeader();

/**
*
* @return List of configured external redirect hosts
*/
@NotNull
List<String> getExternalRedirectHosts();

/**
*
* @return List of configured external resource hosts
*/
@Deprecated // Left for upgrade code only
@NotNull
List<String> getExternalSourceHosts();

Expand All @@ -259,4 +259,6 @@ static WriteableAppProps getWriteableInstance()
@NotNull Set<SupportedDatabase> getDistributionSupportedDatabases();

@NotNull List<String> getAllowedExtensions();

@NotNull String getAllowedExternalResourceHosts();
}
26 changes: 19 additions & 7 deletions api/src/org/labkey/api/settings/AppPropsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -667,13 +667,6 @@ public List<String> getExternalRedirectHosts()
return getExternalHosts(externalRedirectHostURLs);
}

@Override
@NotNull
public List<String> getExternalSourceHosts()
{
return getExternalHosts(externalSourceHostURLs);
}

@Override
@NotNull
public List<String> getAllowedExtensions()
Expand Down Expand Up @@ -732,4 +725,23 @@ public Map<StashedStartupProperties, StartupPropertyEntry> getStashedStartupProp
{
return DISTRIBUTION_SUPPORTED_DATABASES;
}

@Deprecated
@Override
@NotNull
public List<String> getExternalSourceHosts()
{
String urls = lookupStringValue("externalSourceHostURLs", "");
if (StringUtils.isNotBlank(urls))
{
return new ArrayList<>(Arrays.asList(urls.split(EXTERNAL_HOST_DELIMITER)));
}
return new ArrayList<>();
}

@Override
public @NotNull String getAllowedExternalResourceHosts()
{
return lookupStringValue(ALLOWED_EXTERNAL_RESOURCES, "[]");
}
}
8 changes: 0 additions & 8 deletions api/src/org/labkey/api/settings/RandomStartupProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ public void setValue(WriteableAppProps writeable, String value)
writeable.setExternalRedirectHosts(Arrays.asList(StringUtils.split(value, AppPropsImpl.EXTERNAL_HOST_DELIMITER)));
}
},
externalSourceHostURLs("Allowed external source hosts")
{
@Override
public void setValue(WriteableAppProps writeable, String value)
{
writeable.setExternalSourceHosts(Arrays.asList(StringUtils.split(value, AppPropsImpl.EXTERNAL_HOST_DELIMITER)));
}
},
allowedFileExtensions("Allowed file extensions")
{
@Override
Expand Down
10 changes: 5 additions & 5 deletions api/src/org/labkey/api/settings/WriteableAppProps.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,6 @@ public void setExternalRedirectHosts(@NotNull Collection<String> externalRedirec
setExternalHosts(externalRedirectHostURLs, externalRedirectHosts);
}

public void setExternalSourceHosts(@NotNull Collection<String> externalSourceHosts)
{
setExternalHosts(externalSourceHostURLs, externalSourceHosts);
}

private void setExternalHosts(RandomStartupProperties propName, @NotNull Collection<String> externalSourceHosts)
{
storeStringValue(propName, String.join(EXTERNAL_HOST_DELIMITER, externalSourceHosts));
Expand All @@ -247,4 +242,9 @@ public void setAllowedFileExtensions(Collection<String> allowedFileExtensions)
setExternalHosts(RandomStartupProperties.allowedFileExtensions, allowedFileExtensions);
FileUtil.setExtensionChecker(AppProps.getInstance());
}

public void setAllowedExternalResourceHosts(String jsonArray)
{
storeStringValue(ALLOWED_EXTERNAL_RESOURCES, jsonArray);
}
}
Loading