Skip to content

Commit 1623835

Browse files
autumnfoundnetomi
andauthored
feat: Add a bulk publisher revocation endpoint (#1860)
* feat: Add a bulk publisher revocation endpoint This endpoint, using the admin access token as authentication, allows for multiple publishers to be unpublished simulataneously. This additionally adds optional support for adding a reason to the audit log for revoking publisher contributions. Resolves EclipseFdn/open-vsx.org#10109 * feat: Update publisher revoke to remove namespace memberships To ensure full removal of revoked publishers, removal of namespace memberships was added to the publisher revocation flow. * fix: Update publisher revocation to not function on admins As a new guard rail, any publishers that are also admins pushed for revocation via the API gets rejected and must first have their admin role revoked. * fix: Add missing deleteMemberships call in RepositoryService smoke test * fix: Cleanup metadata around bulk revoke, add limits, move logic to admins service * fix: Add provider to the key for bulk publisher results * move eclipse ignores to top-level * fix formatting, move method to API due to transactional self-invocation * adjust tests * fix test --------- Signed-off-by: Thomas Neidhart <thomas.neidhart@eclipse-foundation.org> Co-authored-by: Thomas Neidhart <thomas.neidhart@eclipse-foundation.org>
1 parent 29a24d1 commit 1623835

11 files changed

Lines changed: 549 additions & 128 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.idea/
2+
.project/
3+
.settings/
24
data/

server/.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
.gradle/
55
.idea/
6-
.settings/
76
.classpath
8-
.project
97
.metals
108
.bloop
119

server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java

Lines changed: 166 additions & 121 deletions
Large diffs are not rendered by default.

server/src/main/java/org/eclipse/openvsx/admin/AdminService.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.time.ZoneId;
2121
import java.util.ArrayList;
2222
import java.util.Comparator;
23+
import java.util.HashMap;
2324
import java.util.LinkedHashSet;
2425
import java.util.List;
2526
import java.util.Objects;
@@ -40,6 +41,8 @@
4041
import org.eclipse.openvsx.entities.Namespace;
4142
import org.eclipse.openvsx.entities.PersonalAccessToken;
4243
import org.eclipse.openvsx.entities.UserData;
44+
import org.eclipse.openvsx.json.BulkPublisherRevokeRequestJson;
45+
import org.eclipse.openvsx.json.BulkPublisherRevokeResponseJson;
4346
import org.eclipse.openvsx.json.ChangeNamespaceJson;
4447
import org.eclipse.openvsx.json.ExtensionJson;
4548
import org.eclipse.openvsx.json.NamespaceJson;
@@ -536,6 +539,11 @@ public ResultJson updateUserRole(String provider, String loginName, String role,
536539

537540
@Transactional(rollbackOn = ErrorResultException.class)
538541
public ResultJson revokePublisherContributions(String provider, String loginName, UserData admin) {
542+
return revokePublisherContributions(provider, loginName, admin, null);
543+
}
544+
545+
@Transactional(rollbackOn = ErrorResultException.class)
546+
public ResultJson revokePublisherContributions(String provider, String loginName, UserData admin, String reason) {
539547
var user = repositories.findUserByLoginName(provider, loginName);
540548
if (user == null) {
541549
throw new ErrorResultException(userNotFoundMessage(loginName), HttpStatus.NOT_FOUND);
@@ -570,10 +578,25 @@ public ResultJson revokePublisherContributions(String provider, String loginName
570578
for (var extension : affectedExtensions) {
571579
extensions.updateExtension(extension);
572580
}
581+
582+
// revoke namespace memberships
583+
var namespaceMemberships = repositories.findMemberships(user);
584+
var numberOfNamespaceMemberships = 0L;
585+
// add a null check due to tests using mocks which return null
586+
if (namespaceMemberships != null) {
587+
numberOfNamespaceMemberships = namespaceMemberships.stream().count();
588+
repositories.deleteMemberships(user);
589+
}
590+
591+
var message = "Deactivated " + deactivatedTokenCount + " tokens, "
592+
+ "deactivated " + deactivatedExtensionCount + " extensions, "
593+
+ "removed " + numberOfNamespaceMemberships + " namespace memberships of user "
594+
+ provider + "/" + loginName + ".";
573595

574-
var result = ResultJson.success("Deactivated " + deactivatedTokenCount
575-
+ " tokens, deactivated " + deactivatedExtensionCount + " extensions of user "
576-
+ provider + "/" + loginName + ".");
596+
if (reason != null) {
597+
message += " Reason: " + reason;
598+
}
599+
var result = ResultJson.success(message);
577600
logs.logAction(admin, result);
578601
return result;
579602
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/******************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* https://www.eclipse.org/legal/epl-2.0.
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*****************************************************************************/
13+
package org.eclipse.openvsx.json;
14+
15+
import java.util.List;
16+
17+
import com.fasterxml.jackson.annotation.JsonInclude;
18+
19+
import io.swagger.v3.oas.annotations.media.Schema;
20+
21+
/**
22+
* Used to revoke publishers in bulk
23+
*/
24+
@Schema(
25+
name = "BulkPublisherRevokeRequest",
26+
description = "List of publishers to revoke contributions for"
27+
)
28+
@JsonInclude(JsonInclude.Include.NON_NULL)
29+
public record BulkPublisherRevokeRequestJson(
30+
List<PublisherRevocationTargetJson> publishers,
31+
String reason
32+
) {}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/******************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* https://www.eclipse.org/legal/epl-2.0.
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*****************************************************************************/
13+
package org.eclipse.openvsx.json;
14+
15+
import java.util.Map;
16+
17+
import com.fasterxml.jackson.annotation.JsonInclude;
18+
19+
import io.swagger.v3.oas.annotations.media.Schema;
20+
21+
/**
22+
* Response for the request to bulk revoke publishers
23+
*/
24+
@Schema(
25+
name = "BulkPublisherRevokeResponse",
26+
description = "List of responses for the bulk publisher revocation request"
27+
)
28+
@JsonInclude(JsonInclude.Include.NON_NULL)
29+
public class BulkPublisherRevokeResponseJson extends ResultJson {
30+
31+
public static BulkPublisherRevokeResponseJson error(String message) {
32+
var result = new BulkPublisherRevokeResponseJson();
33+
result.setError(message);
34+
return result;
35+
}
36+
37+
@Schema(description = "Results for each of the attempted revoke operations matched on the login name of the user")
38+
private Map<String, ResultJson> responses;
39+
40+
public BulkPublisherRevokeResponseJson() {
41+
this.responses = null;
42+
}
43+
44+
public BulkPublisherRevokeResponseJson(Map<String, ResultJson> responses) {
45+
this.responses = responses;
46+
}
47+
48+
public void setResponses(Map<String, ResultJson> responses) {
49+
this.responses = responses;
50+
}
51+
52+
public Map<String, ResultJson> getResponses() {
53+
return this.responses;
54+
}
55+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/******************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* https://www.eclipse.org/legal/epl-2.0.
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*****************************************************************************/
13+
package org.eclipse.openvsx.json;
14+
15+
import com.fasterxml.jackson.annotation.JsonInclude;
16+
17+
import io.swagger.v3.oas.annotations.media.Schema;
18+
19+
@Schema(
20+
name = "PublisherRevocationTarget",
21+
description = "Coordinate for a publisher that should be have its' contributions revoked"
22+
)
23+
@JsonInclude(JsonInclude.Include.NON_NULL)
24+
public record PublisherRevocationTargetJson(
25+
String loginName,
26+
String provider
27+
) {}

server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ public interface NamespaceMembershipRepository extends Repository<NamespaceMembe
2626
Streamable<NamespaceMembership> findByUserOrderByNamespaceName(UserData user);
2727

2828
NamespaceMembership findFirstByNamespaceNameIgnoreCase(String namespaceName);
29+
30+
void deleteByUser(UserData user);
2931
}

server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ public Page<UserData> searchUsers(String search, String role, Pageable pageable)
376376
public NamespaceMembership findMembership(UserData user, Namespace namespace) {
377377
return membershipRepo.findByUserAndNamespace(user, namespace);
378378
}
379+
380+
public void deleteMemberships(UserData user) {
381+
membershipRepo.deleteByUser(user);
382+
}
379383

380384
public boolean hasMembership(UserData user, Namespace namespace) {
381385
return membershipJooqRepo.hasMembership(user, namespace);

0 commit comments

Comments
 (0)