Skip to content

Commit db9a98c

Browse files
author
amvanbaren
committed
Revert "Revert "Allow admins to revoke a user's Personal Access Token""
This reverts commit d8a626f.
1 parent c4f48a9 commit db9a98c

File tree

17 files changed

+391
-6
lines changed

17 files changed

+391
-6
lines changed

server/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ dependencies {
8686
implementation "org.springframework.boot:spring-boot-starter-actuator"
8787
implementation "org.springframework.boot:spring-boot-starter-cache"
8888
implementation "org.springframework.boot:spring-boot-starter-aop"
89+
implementation "org.springframework.boot:spring-boot-starter-mail"
90+
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
8991
implementation "org.springframework.security:spring-security-oauth2-client"
9092
implementation "org.springframework.security:spring-security-oauth2-jose"
9193
implementation "org.springframework.session:spring-session-jdbc"

server/src/dev/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,8 @@ ovsx:
162162
storage:
163163
local:
164164
directory: /tmp
165+
mail:
166+
167+
revoked-access-tokens:
168+
subject: 'Open VSX Access Tokens Revoked'
169+
template: 'revoked-access-tokens.html'

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,4 +483,18 @@ public ResponseEntity<ResultJson> revokePublisherContributions(@PathVariable Str
483483
return exc.toResponseEntity();
484484
}
485485
}
486+
487+
@PostMapping(
488+
path = "/admin/publisher/{provider}/{loginName}/tokens/revoke",
489+
produces = MediaType.APPLICATION_JSON_VALUE
490+
)
491+
public ResponseEntity<ResultJson> revokePublisherTokens(@PathVariable String loginName, @PathVariable String provider) {
492+
try {
493+
var adminUser = admins.checkAdminUser();
494+
var result = admins.revokePublisherTokens(provider, loginName, adminUser);
495+
return ResponseEntity.ok(result);
496+
} catch (ErrorResultException exc) {
497+
return exc.toResponseEntity();
498+
}
499+
}
486500
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.eclipse.openvsx.eclipse.EclipseService;
2020
import org.eclipse.openvsx.entities.*;
2121
import org.eclipse.openvsx.json.*;
22+
import org.eclipse.openvsx.mail.MailService;
2223
import org.eclipse.openvsx.migration.HandlerJobRequest;
2324
import org.eclipse.openvsx.repositories.RepositoryService;
2425
import org.eclipse.openvsx.search.SearchUtilService;
@@ -52,6 +53,7 @@ public class AdminService {
5253
private final StorageUtilService storageUtil;
5354
private final CacheService cache;
5455
private final JobRequestScheduler scheduler;
56+
private final MailService mail;
5557

5658
public AdminService(
5759
RepositoryService repositories,
@@ -63,7 +65,8 @@ public AdminService(
6365
EclipseService eclipse,
6466
StorageUtilService storageUtil,
6567
CacheService cache,
66-
JobRequestScheduler scheduler
68+
JobRequestScheduler scheduler,
69+
MailService mail
6770
) {
6871
this.repositories = repositories;
6972
this.extensions = extensions;
@@ -75,6 +78,7 @@ public AdminService(
7578
this.storageUtil = storageUtil;
7679
this.cache = cache;
7780
this.scheduler = scheduler;
81+
this.mail = mail;
7882
}
7983

8084
@EventListener
@@ -388,6 +392,20 @@ public ResultJson revokePublisherContributions(String provider, String loginName
388392
return result;
389393
}
390394

395+
@Transactional(rollbackOn = ErrorResultException.class)
396+
public ResultJson revokePublisherTokens(String provider, String loginName, UserData admin) {
397+
var user = repositories.findUserByLoginName(provider, loginName);
398+
if (user == null) {
399+
throw new ErrorResultException(userNotFoundMessage(loginName), HttpStatus.NOT_FOUND);
400+
}
401+
402+
var deactivatedTokenCount = repositories.deactivateAccessTokens(user);
403+
var result = ResultJson.success("Deactivated " + deactivatedTokenCount + " tokens of user " + provider + "/" + loginName + ".");
404+
logAdminAction(admin, result);
405+
mail.scheduleRevokedAccessTokensMail(user);
406+
return result;
407+
}
408+
391409
public UserData checkAdminUser() {
392410
return checkAdminUser(users.findLoggedInUser());
393411
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2025 Precies. Software OU and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.mail;
11+
12+
import org.springframework.context.ApplicationContext;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
16+
import org.thymeleaf.templatemode.TemplateMode;
17+
18+
@Configuration
19+
public class MailConfig {
20+
21+
@Bean
22+
public SpringResourceTemplateResolver templateResolver(ApplicationContext applicationContext){
23+
var templateResolver = new SpringResourceTemplateResolver();
24+
templateResolver.setApplicationContext(applicationContext);
25+
templateResolver.setPrefix("classpath:/mail-templates/");
26+
templateResolver.setSuffix(".html");
27+
templateResolver.setTemplateMode(TemplateMode.HTML);
28+
return templateResolver;
29+
}
30+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2025 Precies. Software OU and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.mail;
11+
12+
import org.eclipse.openvsx.entities.UserData;
13+
import org.jobrunr.scheduling.JobRequestScheduler;
14+
import org.springframework.beans.factory.annotation.Autowired;
15+
import org.springframework.beans.factory.annotation.Value;
16+
import org.springframework.mail.javamail.JavaMailSender;
17+
import org.springframework.stereotype.Component;
18+
19+
import java.util.Map;
20+
21+
@Component
22+
public class MailService {
23+
24+
private final boolean disabled;
25+
private final JobRequestScheduler scheduler;
26+
27+
@Value("${ovsx.mail.revoked-access-tokens.subject:}")
28+
String revokedAccessTokensSubject;
29+
30+
@Value("${ovsx.mail.revoked-access-tokens.template:}")
31+
String revokedAccessTokensTemplate;
32+
33+
public MailService(@Autowired(required = false) JavaMailSender sender, JobRequestScheduler scheduler) {
34+
this.disabled = sender == null;
35+
this.scheduler = scheduler;
36+
}
37+
38+
public void scheduleRevokedAccessTokensMail(UserData user) {
39+
if(disabled) {
40+
return;
41+
}
42+
43+
var variables = Map.<String, Object>of("name", user.getFullName());
44+
var jobRequest = new SendMailJobRequest(
45+
user.getEmail(),
46+
revokedAccessTokensSubject,
47+
revokedAccessTokensTemplate,
48+
variables
49+
);
50+
51+
scheduler.enqueue(jobRequest);
52+
}
53+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2025 Precies. Software OU and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.mail;
11+
12+
import org.jobrunr.jobs.lambdas.JobRequest;
13+
import org.jobrunr.jobs.lambdas.JobRequestHandler;
14+
15+
import java.util.Map;
16+
17+
public class SendMailJobRequest implements JobRequest {
18+
19+
private String to;
20+
private String subject;
21+
private String template;
22+
private Map<String,Object> variables;
23+
24+
public SendMailJobRequest() {}
25+
26+
public SendMailJobRequest(
27+
String to,
28+
String subject,
29+
String template,
30+
Map<String,Object> variables
31+
) {
32+
this.to = to;
33+
this.subject = subject;
34+
this.template = template;
35+
this.variables = variables;
36+
}
37+
38+
public String getTo() {
39+
return to;
40+
}
41+
42+
public void setTo(String to) {
43+
this.to = to;
44+
}
45+
46+
public String getSubject() {
47+
return subject;
48+
}
49+
50+
public void setSubject(String subject) {
51+
this.subject = subject;
52+
}
53+
54+
public String getTemplate() {
55+
return template;
56+
}
57+
58+
public void setTemplate(String template) {
59+
this.template = template;
60+
}
61+
62+
public Map<String, Object> getVariables() {
63+
return variables;
64+
}
65+
66+
public void setVariables(Map<String, Object> variables) {
67+
this.variables = variables;
68+
}
69+
70+
@Override
71+
public Class<? extends JobRequestHandler> getJobRequestHandler() {
72+
return SendMailJobRequestHandler.class;
73+
}
74+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2025 Precies. Software OU and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.mail;
11+
12+
import org.jobrunr.jobs.lambdas.JobRequestHandler;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.mail.javamail.JavaMailSender;
16+
import org.springframework.mail.javamail.MimeMessageHelper;
17+
import org.springframework.stereotype.Component;
18+
import org.thymeleaf.TemplateEngine;
19+
import org.thymeleaf.context.Context;
20+
21+
import java.nio.charset.StandardCharsets;
22+
23+
@Component
24+
public class SendMailJobRequestHandler implements JobRequestHandler<SendMailJobRequest> {
25+
26+
private final JavaMailSender sender;
27+
private final TemplateEngine templateEngine;
28+
29+
@Value("${ovsx.mail.from:}")
30+
String from;
31+
32+
public SendMailJobRequestHandler(@Autowired(required = false) JavaMailSender sender, TemplateEngine templateEngine) {
33+
this.sender = sender;
34+
this.templateEngine = templateEngine;
35+
}
36+
37+
@Override
38+
public void run(SendMailJobRequest request) throws Exception {
39+
var context = new Context();
40+
context.setVariables(request.getVariables());
41+
var htmlContent = templateEngine.process(request.getTemplate(), context);
42+
43+
var message = sender.createMimeMessage();
44+
var helper = new MimeMessageHelper(message, StandardCharsets.UTF_8.name());
45+
helper.setFrom(from);
46+
helper.setTo(request.getTo());
47+
helper.setSubject(request.getSubject());
48+
helper.setText(htmlContent, true);
49+
sender.send(message);
50+
}
51+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import org.eclipse.openvsx.entities.PersonalAccessToken;
1313
import org.eclipse.openvsx.entities.UserData;
14+
import org.springframework.data.jpa.repository.Modifying;
15+
import org.springframework.data.jpa.repository.Query;
1416
import org.springframework.data.repository.Repository;
1517
import org.springframework.data.util.Streamable;
1618

@@ -29,4 +31,8 @@ public interface PersonalAccessTokenRepository extends Repository<PersonalAccess
2931
PersonalAccessToken findByValue(String value);
3032

3133
PersonalAccessToken findByUserAndDescriptionAndActiveTrue(UserData user, String description);
34+
35+
@Modifying
36+
@Query("update PersonalAccessToken t set t.active = false where t.user = ?1 and t.active = true")
37+
int updateActiveSetFalse(UserData user);
3238
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,10 @@ public void deactivateKeyPairs() {
571571
signatureKeyPairRepo.updateActiveSetFalse();
572572
}
573573

574+
public int deactivateAccessTokens(UserData user) {
575+
return tokenRepo.updateActiveSetFalse(user);
576+
}
577+
574578
public List<String> findActiveExtensionNames(Namespace namespace) {
575579
return extensionJooqRepo.findActiveExtensionNames(namespace);
576580
}

0 commit comments

Comments
 (0)