Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def versions = [
jaxb_impl: '2.3.8',
gatling: '3.14.9',
loki4j: '1.4.2',
jedis: '6.2.0'
jedis: '6.2.0',
re2j: '1.7'
]
ext['junit-jupiter.version'] = versions.junit
java {
Expand Down Expand Up @@ -130,11 +131,14 @@ dependencies {
implementation "com.fasterxml.jackson.module:jackson-module-jaxb-annotations:${versions.jackson}"
implementation "com.fasterxml.woodstox:woodstox-core:${versions.woodstox}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${versions.jackson}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-toml:${versions.jackson}"
implementation "javax.xml.bind:jaxb-api:${versions.jaxb_api}"
implementation "com.sun.xml.bind:jaxb-impl:${versions.jaxb_impl}"
implementation "org.apache.commons:commons-lang3:${versions.commons_lang3}"
implementation "org.apache.httpcomponents.client5:httpclient5"
implementation "org.apache.tika:tika-core:${versions.tika}"
implementation "com.google.re2j:re2j:${versions.re2j}"
implementation "com.github.loki4j:loki-logback-appender:${versions.loki4j}"
implementation "io.micrometer:micrometer-tracing"
implementation "io.micrometer:micrometer-tracing-bridge-otel"
Expand Down
27 changes: 27 additions & 0 deletions server/src/dev/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,30 @@ ovsx:
revoked-access-tokens:
subject: 'Open VSX Access Tokens Revoked'
template: 'revoked-access-tokens.html'
scanning:
enabled: true
similarity:
enabled: true
enforced: true
levenshtein-threshold: 0.2
skip-verified-publishers: true
check-against-verified-only: true
exclude-owner-namespaces: true
new-extensions-only: true
secret-scanning:
enabled: true
rules-path: 'classpath:scanning/secret-scanning-custom-rules.yaml'
inline-suppressions: 'secret-scanner:ignore,gitleaks:allow,nosecret,@suppress-secret'
auto-generate-rules: true
force-regenerate-rules: false
generated-rules-path: '/tmp/secret-scanning-rules-gitleaks.yaml'
max-file-size-bytes: 5242880
max-entry-count: 50000
max-total-uncompressed-bytes: 524288000
max-findings: 200
max-line-length: 10000
long-line-no-space-threshold: 1000
keyword-context-chars: 100
log-allowlisted-value-preview-length: 10
timeout-seconds: 5
timeout-check-every-n-lines: 100
12 changes: 11 additions & 1 deletion server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ public String getVersion() {
return vsixManifest.path(MANIFEST_METADATA).path(MANIFEST_IDENTITY).path("Version").asText();
}

private String getTargetPlatform() {
public String getTargetPlatform() {
loadVsixManifest();
var targetPlatform = vsixManifest.path(MANIFEST_METADATA).path(MANIFEST_IDENTITY).path("TargetPlatform").asText();
if (targetPlatform.isEmpty()) {
targetPlatform = TargetPlatform.NAME_UNIVERSAL;
Expand All @@ -271,6 +272,15 @@ private String getTargetPlatform() {
return targetPlatform;
}

public String getDisplayName() {
loadVsixManifest();
var displayName = vsixManifest.path(MANIFEST_METADATA).path("DisplayName").asText();
if (StringUtils.isBlank(displayName)) {
return getExtensionName();
}
return displayName;
}

private List<String> getTags() {
var tags = vsixManifest.path(MANIFEST_METADATA).path("Tags").asText();
return asStringList(tags, ",").stream()
Expand Down
52 changes: 45 additions & 7 deletions server/src/main/java/org/eclipse/openvsx/ExtensionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.openvsx.json.TargetPlatformVersionJson;
import org.eclipse.openvsx.publish.PublishExtensionVersionHandler;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.scanning.ExtensionScanService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.NamingUtil;
Expand All @@ -46,7 +47,6 @@

@Component
public class ExtensionService {

private static final int MAX_CONTENT_SIZE = 512 * 1024 * 1024;

private final EntityManager entityManager;
Expand All @@ -55,6 +55,7 @@ public class ExtensionService {
private final CacheService cache;
private final PublishExtensionVersionHandler publishHandler;
private final JobRequestScheduler scheduler;
private final ExtensionScanService scanService;

@Value("${ovsx.publishing.require-license:false}")
boolean requireLicense;
Expand All @@ -68,14 +69,16 @@ public ExtensionService(
SearchUtilService search,
CacheService cache,
PublishExtensionVersionHandler publishHandler,
JobRequestScheduler scheduler
JobRequestScheduler scheduler,
ExtensionScanService scanService
) {
this.entityManager = entityManager;
this.repositories = repositories;
this.search = search;
this.cache = cache;
this.publishHandler = publishHandler;
this.scheduler = scheduler;
this.scanService = scanService;
}

@Transactional
Expand All @@ -86,12 +89,47 @@ public ExtensionVersion mirrorVersion(TempFile extensionFile, String signatureNa
}

public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) throws ErrorResultException {
if (scanService.isEnabled()) {
return publishVersionWithScan(content, token);
} else {
var extensionFile = createExtensionFile(content);
doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.publishAsync(extensionFile, this);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(download);
return download.getExtension();
}
}

private ExtensionVersion publishVersionWithScan(InputStream content, PersonalAccessToken token) throws ErrorResultException {
var extensionFile = createExtensionFile(content);
doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.publishAsync(extensionFile, this);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(download);
return download.getExtension();
ExtensionScan scan = null;

try (var processor = new ExtensionProcessor(extensionFile)) {
scan = scanService.initializeScan(processor, token.getUser());

scanService.runValidation(scan, extensionFile, token.getUser());

doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);

scanService.runScan(scan, extensionFile, token.getUser());

publishHandler.publishAsync(extensionFile, this, scan);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(extensionFile.getResource());
return download.getExtension();
} catch (ErrorResultException e) {
// ErrorResultException is thrown by doPublish when the extension is not valid, so we can remove the scan
if (scan != null && !scan.isCompleted()) {
scanService.removeScan(scan);
}
throw e;
} catch (Exception e) {
if (scan != null && !scan.isCompleted()) {
scanService.markScanAsErrored(scan, "Unexpected error: " + e.getMessage());
}
throw e;
}
}

private void doPublish(TempFile extensionFile, String binaryName, PersonalAccessToken token, LocalDateTime timestamp, boolean checkDependencies) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import org.eclipse.openvsx.search.ISearchService;
import org.eclipse.openvsx.search.SearchResult;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.search.SimilarityCheckService;
import org.eclipse.openvsx.storage.StorageUtilService;
import javax.annotation.Nullable;
import org.eclipse.openvsx.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -63,6 +65,7 @@ public class LocalRegistryService implements IExtensionRegistry {
private final EclipseService eclipse;
private final CacheService cache;
private final ExtensionVersionIntegrityService integrityService;
private final SimilarityCheckService similarityCheckService;

public LocalRegistryService(
EntityManager entityManager,
Expand All @@ -75,7 +78,8 @@ public LocalRegistryService(
StorageUtilService storageUtil,
EclipseService eclipse,
CacheService cache,
ExtensionVersionIntegrityService integrityService
ExtensionVersionIntegrityService integrityService,
@Nullable SimilarityCheckService similarityCheckService
) {
this.entityManager = entityManager;
this.repositories = repositories;
Expand All @@ -88,6 +92,7 @@ public LocalRegistryService(
this.eclipse = eclipse;
this.cache = cache;
this.integrityService = integrityService;
this.similarityCheckService = similarityCheckService;
}

@Value("${ovsx.webui.url:}")
Expand Down Expand Up @@ -595,6 +600,21 @@ public ResultJson createNamespace(NamespaceJson json, UserData user) {
throw new ErrorResultException("Namespace already exists: " + namespaceName);
}

// Check if the proposed namespace name is too similar to existing ones (if enabled)
if (similarityCheckService != null && similarityCheckService.isEnabled()) {
var similarNamespaces = similarityCheckService.findSimilarNamespacesForCreation(json.getName(), user);
if (!similarNamespaces.isEmpty()) {
var similarNames = similarNamespaces.stream()
.map(Namespace::getName)
.collect(Collectors.joining(", "));
throw new ErrorResultException(
"Namespace name '" + json.getName() + "' is too similar to existing namespace(s): " + similarNames + ". " +
"Please choose a more distinct name to avoid confusion. " +
"Refer to the publishing guidelines: https://github.com/EclipseFdn/open-vsx.org/wiki/Publishing-Extensions"
);
}
}

// Create the requested namespace
var namespace = new Namespace();
namespace.setName(json.getName());
Expand Down
Loading