Skip to content

Commit 9674e8a

Browse files
authored
Fix delete of allowed external resource hosts (#6841)
1 parent 2207ca8 commit 9674e8a

3 files changed

Lines changed: 49 additions & 30 deletions

File tree

core/src/org/labkey/core/CoreUpgradeCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ public static void migrateAllowedExternalConnectionHosts(ModuleContext context)
174174
.map(host -> new AllowedHost(Directive.Connection, host))
175175
.toList();
176176

177+
// No need to synchronize since upgrade is single-threaded
177178
AllowedExternalResourceHosts.saveAllowedHosts(allowedHosts, context.getUpgradeUser());
178179
}
179180
}

core/src/org/labkey/core/admin/AdminController.java

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11380,46 +11380,52 @@ public ModelAndView getView(ExternalSourcesForm form, boolean reshow, BindExcept
1138011380
return new VBox(newView, existingView);
1138111381
}
1138211382

11383+
private static final Object HOST_LOCK = new Object();
11384+
1138311385
@Override
1138411386
public boolean handlePost(ExternalSourcesForm form, BindException errors) throws Exception
1138511387
{
1138611388
List<AllowedHost> allowedHosts = null;
1138711389

11388-
//handle delete of existing value
11389-
if (form.isDelete())
11390+
// Multiple requests could access this in parallel, so synchronize access, Issue 53457
11391+
synchronized (HOST_LOCK)
1139011392
{
11391-
AllowedHost subToDelete = form.getExistingAllowedHost(errors);
11392-
if (errors.hasErrors())
11393-
return false;
11394-
allowedHosts = form.getSavedAllowedHosts();
11395-
var iter = allowedHosts.listIterator();
11396-
while (iter.hasNext())
11393+
//handle delete of an existing value
11394+
if (form.isDelete())
1139711395
{
11398-
AllowedHost sub = iter.next();
11399-
if (sub.equals(subToDelete))
11396+
AllowedHost subToDelete = form.getExistingAllowedHost(errors);
11397+
if (errors.hasErrors())
11398+
return false;
11399+
allowedHosts = form.getSavedAllowedHosts();
11400+
var iter = allowedHosts.listIterator();
11401+
while (iter.hasNext())
1140011402
{
11401-
iter.remove();
11402-
break;
11403+
AllowedHost sub = iter.next();
11404+
if (sub.equals(subToDelete))
11405+
{
11406+
iter.remove();
11407+
break;
11408+
}
1140311409
}
1140411410
}
11405-
}
11406-
//handle updates - clicking on Save button under Existing will save the updated urls
11407-
else if (form.isSaveAll())
11408-
{
11409-
allowedHosts = form.getExistingAllowedHosts(errors);
11411+
//handle updates - clicking on Save button under Existing will save the updated hosts
11412+
else if (form.isSaveAll())
11413+
{
11414+
allowedHosts = form.getExistingAllowedHosts(errors);
11415+
if (errors.hasErrors())
11416+
return false;
11417+
}
11418+
//save new external value
11419+
else if (form.isSaveNew())
11420+
{
11421+
allowedHosts = form.validateNewAllowedHost(errors);
11422+
}
11423+
1141011424
if (errors.hasErrors())
1141111425
return false;
11412-
}
11413-
//save new external value
11414-
else if (form.isSaveNew())
11415-
{
11416-
allowedHosts = form.validateNewAllowedHost(errors);
11417-
}
1141811426

11419-
if (errors.hasErrors())
11420-
return false;
11421-
11422-
AllowedExternalResourceHosts.saveAllowedHosts(allowedHosts, getUser());
11427+
AllowedExternalResourceHosts.saveAllowedHosts(allowedHosts, getUser());
11428+
}
1142311429

1142411430
return true;
1142511431
}

core/src/org/labkey/core/security/AllowedExternalResourceHosts.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ private AllowedExternalResourceHosts()
3737

3838
public record AllowedHost(Directive directive, String host) { }
3939

40+
// Callers must ensure thread-safe access to this method
4041
public static void saveAllowedHosts(@Nullable Collection<AllowedHost> allowedHosts, User user)
4142
{
4243
if (null != allowedHosts)
@@ -63,14 +64,24 @@ public static void saveAllowedHosts(@Nullable Collection<AllowedHost> allowedHos
6364

6465
// Unregister all supported directives then register the directives that have at least one allowed host
6566
Arrays.stream(Directive.values()).forEach(dir -> {
66-
ContentSecurityPolicyFilter.unregisterAllowedSources(ALLOWED_EXTERNAL_RESOURCES, dir);
67+
unregister(dir);
6768
List<String> list = map.get(dir);
6869
if (list != null)
69-
ContentSecurityPolicyFilter.registerAllowedSources(ALLOWED_EXTERNAL_RESOURCES, dir, list.toArray(new String[0]));
70+
register(dir, list.toArray(new String[0]));
7071
});
7172
}
7273
}
7374

75+
private static void register(Directive dir, String... hosts)
76+
{
77+
ContentSecurityPolicyFilter.registerAllowedSources(ALLOWED_EXTERNAL_RESOURCES, dir, hosts);
78+
}
79+
80+
private static void unregister(Directive dir)
81+
{
82+
ContentSecurityPolicyFilter.unregisterAllowedSources(ALLOWED_EXTERNAL_RESOURCES, dir);
83+
}
84+
7485
// Returns a mutable list (mutating it won't affect any cached values)
7586
public static List<AllowedHost> readAllowedHosts() throws JsonProcessingException
7687
{
@@ -92,7 +103,7 @@ public static void registerHosts()
92103
return;
93104
}
94105

95-
list.forEach(sub -> ContentSecurityPolicyFilter.registerAllowedSources("External Sources", sub.directive(), sub.host()));
106+
list.forEach(sub -> register(sub.directive(), sub.host()));
96107
LOG.debug("Registered [{}] as allowed external sources", list);
97108
}
98109

@@ -125,6 +136,7 @@ public void handle(Map<Directive, StartupPropertyEntry> properties)
125136
}
126137
else
127138
{
139+
// No need to synchronize since startup property handling is single-threaded
128140
saveAllowedHosts(allowedHosts, User.getAdminServiceUser());
129141
}
130142
}

0 commit comments

Comments
 (0)