Skip to content

Commit ad98742

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_crossSampleTypeWarnings
2 parents 82daf70 + e203745 commit ad98742

4 files changed

Lines changed: 195 additions & 4 deletions

File tree

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ plugins {
66
}
77

88
import org.labkey.gradle.task.RunTestSuite
9+
import org.labkey.gradle.task.RunUiTest
910
import org.labkey.gradle.util.BuildUtils
1011
import org.labkey.gradle.util.GroupNames
1112
import org.labkey.gradle.util.PomFileHelper
@@ -171,6 +172,10 @@ project.tasks.register("convertHarToStressXml", JavaExec) {
171172
}
172173
}
173174

175+
project.tasks.register("testPackageLockJson", RunUiTest) {
176+
include "org/labkey/test/tests/PackageLockJsonTest.class"
177+
}
178+
174179
project.tasks.named("uiTests").configure {
175180
dependsOn(initPropertiesTask)
176181
}

data/api/rlabkey-api-query.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -629,10 +629,10 @@
629629
<url>
630630
<![CDATA[
631631
library(Rlabkey)
632-
makeFilter(
632+
print(makeFilter(
633633
c("foo/Reagent", "CONTAINS", "this or that"),
634634
c("Temp_C", "GREATER_THAN", "12"),
635-
c("SystolicBloodPressure", "MISSING", ""))
635+
c("SystolicBloodPressure", "MISSING", "")))
636636
]]>
637637
</url>
638638
<response>
@@ -647,10 +647,10 @@
647647
<url>
648648
<![CDATA[
649649
library(Rlabkey)
650-
makeFilter(
650+
print(makeFilter(
651651
c("foo/Reagent", "CONTAINS", "this or that"),
652652
c("Temp_C", "GREATER_THAN", "12"),
653-
c("SystolicBloodPressure", "MISSING", ""), asList=TRUE)
653+
c("SystolicBloodPressure", "MISSING", ""), asList=TRUE))
654654
]]>
655655
</url>
656656
<response>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package org.labkey.test.tests;
2+
3+
import org.apache.commons.lang3.CharUtils;
4+
import org.json.JSONObject;
5+
import org.json.JSONTokener;
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
import org.junit.experimental.categories.Category;
9+
import org.junit.runner.RunWith;
10+
import org.junit.runners.Parameterized;
11+
import org.labkey.serverapi.reader.Readers;
12+
import org.labkey.test.TestFileUtils;
13+
import org.labkey.test.util.TestLogger;
14+
15+
import java.io.File;
16+
import java.io.Reader;
17+
import java.net.URI;
18+
import java.net.URISyntaxException;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Objects;
24+
import java.util.Set;
25+
26+
@Category({})
27+
@RunWith(Parameterized.class)
28+
public class PackageLockJsonTest
29+
{
30+
private static final Set<String> ALLOWED_DEPENDENCY_HOSTS = Set.of("registry.npmjs.org", "labkey.jfrog.io");
31+
// Allow-list of '@isaacs/cliui' dependencies
32+
private static final Set<String> ALLOWED_NONSTANDARD_VERSIONS = Set.of("npm:string-width@^4.2.0", "npm:strip-ansi@^6.0.1", "npm:wrap-ansi@^7.0.0");
33+
34+
private final List<String> errors = new ArrayList<>();
35+
private final File moduleDir;
36+
37+
public PackageLockJsonTest(File moduleDir, String name)
38+
{
39+
this.moduleDir = moduleDir;
40+
}
41+
42+
@Parameterized.Parameters(name = "{1}")
43+
public static Collection<Object[]> data()
44+
{
45+
List<File> allModules = new ArrayList<>();
46+
47+
File modulesDir = new File(TestFileUtils.getLabKeyRoot(), "server/modules");
48+
File[] files = modulesDir.listFiles();
49+
if (files == null)
50+
{
51+
throw new RuntimeException("No files found in modules directory: " + modulesDir.getAbsolutePath());
52+
}
53+
for (File file : files)
54+
{
55+
if (file.isDirectory())
56+
{
57+
if (new File(file, "module.properties").exists())
58+
{
59+
allModules.add(file);
60+
}
61+
else
62+
{
63+
allModules.addAll(Arrays.stream(Objects.requireNonNull(file.listFiles(), () -> "No files found in " + file.getAbsolutePath()))
64+
.filter(f -> f.isDirectory() && new File(f, "module.properties").isFile()).toList());
65+
}
66+
}
67+
}
68+
69+
return allModules.stream().filter(file -> new File(file, "package-lock.json").exists())
70+
.map(f -> new Object[]{f, f.getName()}).toList();
71+
}
72+
73+
@Test
74+
public void testPackageLock() throws Exception
75+
{
76+
File packageLock = new File(moduleDir, "package-lock.json");
77+
Assert.assertTrue("No package-lock.json found in module: " + moduleDir.getAbsolutePath(), packageLock.isFile());
78+
79+
TestLogger.log("Testing module: " + moduleDir.getAbsolutePath());
80+
81+
JSONObject packages;
82+
try (Reader reader = Readers.getReader(packageLock))
83+
{
84+
JSONObject jsonObject = new JSONObject(new JSONTokener(reader));
85+
packages = jsonObject.optJSONObject("packages");
86+
if (packages == null)
87+
packages = jsonObject.getJSONObject("dependencies"); // old lockfile version
88+
}
89+
90+
for (String packageName : packages.keySet())
91+
{
92+
if (!packageName.isBlank())
93+
{
94+
JSONObject packageJson = packages.getJSONObject(packageName);
95+
verifyPackage(packageName, packageJson, packageLock);
96+
}
97+
}
98+
99+
Assert.assertTrue("Bad sources: " + errors, errors.isEmpty());
100+
}
101+
102+
/// Verify that a package reference in a package-lock.json file only resolves to known hosts and has a valid version
103+
/// Also checks sub-dependencies
104+
private void verifyPackage(String packageName, JSONObject packageJson, File packageLockFile)
105+
{
106+
String resolved = packageJson.optString("resolved");
107+
if (resolved.isBlank())
108+
{
109+
TestLogger.debug("Resolved field is blank for package " + packageName + " in " + packageLockFile.getAbsolutePath());
110+
}
111+
else
112+
{
113+
try
114+
{
115+
URI resolvedURL = new URI(resolved);
116+
String host = resolvedURL.getHost();
117+
if (!ALLOWED_DEPENDENCY_HOSTS.contains(host))
118+
{
119+
String message = "Package " + packageName + " resolved to unrecognized host [" + host + "] in " + packageLockFile.getAbsolutePath();
120+
errors.add(message);
121+
TestLogger.error(message);
122+
}
123+
}
124+
catch (URISyntaxException e)
125+
{
126+
String message = "Package " + packageName + " resolved to an invalid location [" + resolved + "] in " + packageLockFile.getAbsolutePath();
127+
errors.add(message);
128+
TestLogger.error(message);
129+
}
130+
}
131+
132+
String version = packageJson.optString("version");
133+
if (version.isBlank() || !CharUtils.isAsciiNumeric(version.charAt(0)))
134+
{
135+
String message = "Package " + packageName + " has bad version [" + version + "] in " + packageLockFile.getAbsolutePath();
136+
errors.add(message);
137+
TestLogger.error(message);
138+
}
139+
140+
JSONObject transitiveDeps = packageJson.optJSONObject("dependencies", new JSONObject());
141+
for (String tDep : transitiveDeps.keySet())
142+
{
143+
JSONObject packageJsonDep = transitiveDeps.optJSONObject(tDep);
144+
if (packageJsonDep != null)
145+
{
146+
verifyPackage(tDep, packageJsonDep, packageLockFile); // belt and suspenders
147+
}
148+
else
149+
{
150+
String tVer = transitiveDeps.optString(tDep);
151+
if (tVer == null || tVer.contains(":") && !ALLOWED_NONSTANDARD_VERSIONS.contains(tVer)) // URL, file, or workspace dependency
152+
{
153+
String message = "Package " + packageName + " has bad transitive dependency [" + tVer + "] in " + packageLockFile.getAbsolutePath();
154+
errors.add(message);
155+
TestLogger.error(message);
156+
}
157+
}
158+
}
159+
}
160+
}

src/org/labkey/test/tests/SecurityTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@
4949
import org.openqa.selenium.support.ui.ExpectedConditions;
5050

5151
import java.io.IOException;
52+
import java.io.StringReader;
53+
import java.time.ZonedDateTime;
54+
import java.time.format.DateTimeFormatter;
5255
import java.util.Arrays;
5356
import java.util.Collections;
5457
import java.util.HashSet;
5558
import java.util.List;
59+
import java.util.Properties;
5660
import java.util.Set;
5761

5862
import static org.junit.Assert.assertEquals;
@@ -122,6 +126,28 @@ protected void doCleanup(boolean afterTest) throws TestTimeoutException
122126
DbLoginUtils.resetDbLoginConfig(cn);
123127
}
124128

129+
@Test
130+
public void testSecurityTxt() throws IOException
131+
{
132+
getDriver().navigate().to(WebTestHelper.getBaseURL() + "/.well-known/security.txt");
133+
134+
String body = getBodyText();
135+
136+
Properties props = new Properties();
137+
props.load(new StringReader(body));
138+
139+
assertTrue("security.txt missing Contact", props.containsKey("Contact"));
140+
assertTrue("security.txt missing Policy", props.containsKey("Policy"));
141+
assertTrue("security.txt missing Expires", props.containsKey("Expires"));
142+
143+
String expiresStr = props.getProperty("Expires");
144+
ZonedDateTime expires = ZonedDateTime.parse(expiresStr, DateTimeFormatter.ISO_INSTANT.withZone(java.time.ZoneId.of("UTC")));
145+
ZonedDateTime nextYear = ZonedDateTime.now(java.time.ZoneId.of("UTC")).plusYears(1);
146+
147+
// If this assert fails, edit security.txt to use something more than a year in the future
148+
assertTrue("security.txt 'Expires' value should be more than a year in the future: " + expiresStr, expires.isAfter(nextYear));
149+
}
150+
125151
@Test
126152
public void testSteps() throws IOException
127153
{

0 commit comments

Comments
 (0)