Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea/
data/
/.project
.settings/org.eclipse.buildship.core.prefs
35 changes: 35 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.time.Period;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

Expand All @@ -22,6 +23,8 @@
import org.eclipse.openvsx.entities.NamespaceMembership;
import org.eclipse.openvsx.entities.PersistedLog;
import org.eclipse.openvsx.json.AdminStatisticsJson;
import org.eclipse.openvsx.json.BulkPublisherRevokeRequestJson;
import org.eclipse.openvsx.json.BulkPublisherRevokeResponseJson;
import org.eclipse.openvsx.json.ChangeNamespaceJson;
import org.eclipse.openvsx.json.ExtensionJson;
import org.eclipse.openvsx.json.NamespaceJson;
Expand Down Expand Up @@ -588,6 +591,38 @@ public ResponseEntity<ResultJson> revokePublisherContributions(@PathVariable Str
}
}

@PostMapping(
path = "/admin/api/publisher/revoke",
produces = MediaType.APPLICATION_JSON_VALUE
)
@CrossOrigin
@Operation(summary = "Bulk revoke publisher contributions")
@ApiResponse(
responseCode = "200",
description = "A success message is returned in JSON format",
content = @Content(schema = @Schema(implementation = BulkPublisherRevokeResponseJson.class))
)
public ResponseEntity<BulkPublisherRevokeResponseJson> revokeBulkPublishers(
@RequestParam(value = "token") @Parameter(description = "A personal access token") String tokenValue,
@RequestBody BulkPublisherRevokeRequestJson request
) {
try {
var adminUser = admins.checkAdminUser(tokenValue);
var resultMap = new HashMap<String, ResultJson>();
for (var publisher : request.publishers()) {
try {
var result = admins.revokePublisherContributions(publisher.provider(), publisher.loginName(), adminUser, request.reason());
resultMap.put(publisher.loginName(), result);
} catch (ErrorResultException exc) {
resultMap.put(publisher.loginName(), exc.toResponseEntity().getBody());
}
}
return ResponseEntity.ok(new BulkPublisherRevokeResponseJson(resultMap));
} catch (ErrorResultException exc) {
return exc.toResponseEntity(BulkPublisherRevokeResponseJson.class);
}
}

@PostMapping(
path = "/admin/publisher/{provider}/{loginName}/tokens/revoke",
produces = MediaType.APPLICATION_JSON_VALUE
Expand Down
30 changes: 25 additions & 5 deletions server/src/main/java/org/eclipse/openvsx/admin/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,19 @@ public UserPublishInfoJson getUserPublishInfo(String provider, String loginName)
return userPublishInfo;
}

@Transactional(rollbackOn = ErrorResultException.class)
public ResultJson revokePublisherContributions(String provider, String loginName, UserData admin) {
return revokePublisherContributions(provider, loginName, admin, null);
}

@Transactional(rollbackOn = ErrorResultException.class)
public ResultJson revokePublisherContributions(String provider, String loginName, UserData admin, String reason) {
var user = repositories.findUserByLoginName(provider, loginName);
if (user == null) {
throw new ErrorResultException(userNotFoundMessage(loginName), HttpStatus.NOT_FOUND);
}
if (UserData.ROLE_ADMIN.equals(user.getRole())) {
throw new ErrorResultException("Cannot revoke contributions for admins through publisher flow", HttpStatus.BAD_REQUEST);
}

// Send a DELETE request to the Eclipse publisher agreement API
if (eclipse.isActive() && user.getEclipsePersonId() != null) {
Expand Down Expand Up @@ -518,10 +525,23 @@ public ResultJson revokePublisherContributions(String provider, String loginName
for (var extension : affectedExtensions) {
extensions.updateExtension(extension);
}

var result = ResultJson.success("Deactivated " + deactivatedTokenCount
+ " tokens, deactivated " + deactivatedExtensionCount + " extensions of user "
+ provider + "/" + loginName + ".");

// revoke namespace memberships
var namespaceMemberships = repositories.findMemberships(user);
var numberOfNamespaceMemberships = 0;
if (namespaceMemberships != null) {
numberOfNamespaceMemberships = namespaceMemberships.toSet().size();
}
repositories.deleteMemberships(user);

var message = "Deactivated " + deactivatedTokenCount
+ " tokens, deactivated " + deactivatedExtensionCount + " extensions, "
+ numberOfNamespaceMemberships + " namespace memberships of user "
+ provider + "/" + loginName + ".";
if (reason != null) {
message += " Reason: " + reason;
}
var result = ResultJson.success(message);
logs.logAction(admin, result);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** ******************************************************************************
* Copyright (c) 2026 Eclipse Foundation AISBL.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.json;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

import io.swagger.v3.oas.annotations.media.Schema;

/**
* Used to revoke publishers in bulk
*/
@Schema(
name = "BulkPublisherRevokeRequest",
description = "List of publishers to revoke contributions for"
)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record BulkPublisherRevokeRequestJson(
List<PublisherRevocationTargetJson> publishers,
String reason
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** ******************************************************************************
* Copyright (c) 2026 Eclipse Foundation AISBL.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.json;

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonInclude;

import io.swagger.v3.oas.annotations.media.Schema;

/**
* Response for the request to bulk revoke publishers
*/
@Schema(
name = "BulkPublisherRevokeResponse",
description = "List of responses for the bulk publisher revocation request"
)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BulkPublisherRevokeResponseJson extends ResultJson {

@Schema(description = "Results for each of the attempted revoke operations matched on the login name of the user")
private Map<String, ResultJson> responses;

public BulkPublisherRevokeResponseJson() {
this.responses = null;
}

public BulkPublisherRevokeResponseJson(Map<String, ResultJson> responses) {
this.responses = responses;
}

public void setResponses(Map<String, ResultJson> responses) {
this.responses = responses;
}

public Map<String, ResultJson> getResponses() {
return this.responses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** ******************************************************************************
* Copyright (c) 2026 Eclipse Foundation AISBL.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.json;

import com.fasterxml.jackson.annotation.JsonInclude;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(
name = "PublisherRevocationTarget",
description = "Coordinate for a publisher that should be have its' contributions revoked"
)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record PublisherRevocationTargetJson(
String loginName,
String provider
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ public interface NamespaceMembershipRepository extends Repository<NamespaceMembe
Streamable<NamespaceMembership> findByUserOrderByNamespaceName(UserData user);

NamespaceMembership findFirstByNamespaceNameIgnoreCase(String namespaceName);

void deleteByUser(UserData user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ public long countUsers() {
public NamespaceMembership findMembership(UserData user, Namespace namespace) {
return membershipRepo.findByUserAndNamespace(user, namespace);
}

public void deleteMemberships(UserData user) {
membershipRepo.deleteByUser(user);
}

public boolean hasMembership(UserData user, Namespace namespace) {
return membershipJooqRepo.hasMembership(user, namespace);
Expand Down
Loading
Loading