Skip to content

Commit de299db

Browse files
committed
General: Only send login email notification during actual username and password login
1 parent 3709054 commit de299db

File tree

6 files changed

+87
-108
lines changed

6 files changed

+87
-108
lines changed

src/main/java/de/tum/cit/aet/artemis/core/security/ArtemisAuthenticationEventListener.java renamed to src/main/java/de/tum/cit/aet/artemis/core/service/ArtemisSuccessfulLoginService.java

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package de.tum.cit.aet.artemis.core.security;
1+
package de.tum.cit.aet.artemis.core.service;
22

33
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;
44

@@ -10,11 +10,8 @@
1010
import org.slf4j.Logger;
1111
import org.slf4j.LoggerFactory;
1212
import org.springframework.beans.factory.annotation.Value;
13-
import org.springframework.context.ApplicationListener;
1413
import org.springframework.context.annotation.Profile;
15-
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
16-
import org.springframework.security.core.Authentication;
17-
import org.springframework.stereotype.Component;
14+
import org.springframework.stereotype.Service;
1815

1916
import de.tum.cit.aet.artemis.communication.service.notifications.MailSendingService;
2017
import de.tum.cit.aet.artemis.core.domain.User;
@@ -27,8 +24,8 @@
2724
* to users when they successfully log in.
2825
*/
2926
@Profile(PROFILE_CORE)
30-
@Component
31-
public class ArtemisAuthenticationEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
27+
@Service
28+
public class ArtemisSuccessfulLoginService {
3229

3330
@Value("${artemis.user-management.password-reset.links.en:https://artemis.tum.de/account/reset/request}")
3431
private String passwordResetLinkEnUrl;
@@ -39,28 +36,24 @@ public class ArtemisAuthenticationEventListener implements ApplicationListener<A
3936
@Value("${server.url}")
4037
private URL artemisServerUrl;
4138

42-
private static final Logger log = LoggerFactory.getLogger(ArtemisAuthenticationEventListener.class);
39+
private static final Logger log = LoggerFactory.getLogger(ArtemisSuccessfulLoginService.class);
4340

4441
private final UserRepository userRepository;
4542

4643
private final MailSendingService mailSendingService;
4744

48-
public ArtemisAuthenticationEventListener(UserRepository userRepository, MailSendingService mailSendingService) {
45+
public ArtemisSuccessfulLoginService(UserRepository userRepository, MailSendingService mailSendingService) {
4946
this.userRepository = userRepository;
5047
this.mailSendingService = mailSendingService;
5148
}
5249

5350
/**
5451
* Handles successful authentication events.
5552
* Sends a login notification email to users when they successfully authenticate.
56-
*
57-
* @param event The authentication success event to process
5853
*/
59-
@Override
60-
public void onApplicationEvent(AuthenticationSuccessEvent event) {
61-
Authentication authentication = event.getAuthentication();
54+
public void sendLoginEmail(String username) {
6255
try {
63-
User recipient = userRepository.getUserByLoginElseThrow(authentication.getName());
56+
User recipient = userRepository.getUserByLoginElseThrow(username);
6457
var contextVariables = new HashMap<String, Object>();
6558
ZonedDateTime now = ZonedDateTime.now();
6659
contextVariables.put("loginDate", now.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")));
@@ -86,17 +79,7 @@ public void onApplicationEvent(AuthenticationSuccessEvent event) {
8679
mailSendingService.buildAndSendAsync(recipient, "email.notification.login.title", "mail/notification/newLoginEmail", contextVariables);
8780
}
8881
catch (EntityNotFoundException ignored) {
89-
log.error("User with login {} not found when trying to send newLoginEmail", authentication.getName());
82+
log.error("User with login {} not found when trying to send newLoginEmail", username);
9083
}
9184
}
92-
93-
/**
94-
* Determines whether this listener supports asynchronous execution. Async not needed since email is sent asynchronously.
95-
*
96-
* @return false, indicating this listener should be executed synchronously
97-
*/
98-
@Override
99-
public boolean supportsAsyncExecution() {
100-
return false;
101-
}
10285
}

src/main/java/de/tum/cit/aet/artemis/core/web/open/PublicUserJwtResource.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import de.tum.cit.aet.artemis.core.security.allowedTools.ToolTokenType;
3939
import de.tum.cit.aet.artemis.core.security.annotations.EnforceNothing;
4040
import de.tum.cit.aet.artemis.core.security.jwt.JWTCookieService;
41+
import de.tum.cit.aet.artemis.core.service.ArtemisSuccessfulLoginService;
4142
import de.tum.cit.aet.artemis.core.service.connectors.SAML2Service;
4243

4344
/**
@@ -54,11 +55,15 @@ public class PublicUserJwtResource {
5455

5556
private final AuthenticationManager authenticationManager;
5657

58+
private final ArtemisSuccessfulLoginService artemisSuccessfulLoginService;
59+
5760
private final Optional<SAML2Service> saml2Service;
5861

59-
public PublicUserJwtResource(JWTCookieService jwtCookieService, AuthenticationManager authenticationManager, Optional<SAML2Service> saml2Service) {
62+
public PublicUserJwtResource(JWTCookieService jwtCookieService, AuthenticationManager authenticationManager, ArtemisSuccessfulLoginService artemisSuccessfulLoginService,
63+
Optional<SAML2Service> saml2Service) {
6064
this.jwtCookieService = jwtCookieService;
6165
this.authenticationManager = authenticationManager;
66+
this.artemisSuccessfulLoginService = artemisSuccessfulLoginService;
6267
this.saml2Service = saml2Service;
6368
}
6469

@@ -91,6 +96,9 @@ public ResponseEntity<Map<String, String>> authorize(@Valid @RequestBody LoginVM
9196
ResponseCookie responseCookie = jwtCookieService.buildLoginCookie(rememberMe, tool);
9297
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
9398

99+
// TODO: move this to the actual login implementations
100+
artemisSuccessfulLoginService.sendLoginEmail(username);
101+
94102
return ResponseEntity.ok(Map.of("access_token", responseCookie.getValue()));
95103
}
96104
catch (BadCredentialsException ex) {

src/test/java/de/tum/cit/aet/artemis/communication/notifications/service/MailServiceTest.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import de.tum.cit.aet.artemis.communication.service.notifications.MailSendingService;
2626
import de.tum.cit.aet.artemis.communication.service.notifications.MailService;
2727
import de.tum.cit.aet.artemis.core.domain.User;
28-
import de.tum.cit.aet.artemis.core.service.TimeService;
2928
import tech.jhipster.config.JHipsterProperties;
3029

3130
/**
@@ -35,8 +34,6 @@
3534
*/
3635
class MailServiceTest {
3736

38-
private MailService mailService;
39-
4037
private MailSendingService mailSendingService;
4138

4239
@Mock
@@ -57,13 +54,8 @@ class MailServiceTest {
5754
@Mock
5855
private SpringTemplateEngine templateEngine;
5956

60-
@Mock
61-
private TimeService timeService;
62-
6357
private User student1;
6458

65-
private User student2;
66-
6759
private String subject;
6860

6961
private String content;
@@ -79,7 +71,7 @@ void setUp() throws MalformedURLException, URISyntaxException {
7971
student1.setEmail("[email protected]");
8072
student1.setLangKey("de");
8173

82-
student2 = new User();
74+
User student2 = new User();
8375
student2.setLogin("student2");
8476
student2.setId(556L);
8577
student2.setEmail("[email protected]");
@@ -107,7 +99,7 @@ void setUp() throws MalformedURLException, URISyntaxException {
10799

108100
mailSendingService = new MailSendingService(jHipsterProperties, javaMailSender, messageSource, templateEngine);
109101

110-
mailService = new MailService(messageSource, templateEngine, mailSendingService);
102+
MailService mailService = new MailService(messageSource, templateEngine, mailSendingService);
111103
ReflectionTestUtils.setField(mailService, "artemisServerUrl", new URI("http://localhost:8080").toURL());
112104
}
113105

src/test/java/de/tum/cit/aet/artemis/core/security/ArtemisAuthenticationEventListenerTest.java

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package de.tum.cit.aet.artemis.core.security;
2+
3+
import static java.util.concurrent.TimeUnit.SECONDS;
4+
import static org.awaitility.Awaitility.await;
5+
import static org.mockito.ArgumentMatchers.any;
6+
import static org.mockito.ArgumentMatchers.anyMap;
7+
import static org.mockito.ArgumentMatchers.anyString;
8+
import static org.mockito.ArgumentMatchers.eq;
9+
import static org.mockito.Mockito.doNothing;
10+
import static org.mockito.Mockito.never;
11+
import static org.mockito.Mockito.verify;
12+
13+
import jakarta.mail.internet.MimeMessage;
14+
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.extension.ExtendWith;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.security.test.context.support.WithMockUser;
21+
22+
import de.tum.cit.aet.artemis.core.domain.User;
23+
import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException;
24+
import de.tum.cit.aet.artemis.core.service.ArtemisSuccessfulLoginService;
25+
import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest;
26+
27+
@ExtendWith(MockitoExtension.class)
28+
class ArtemisSuccessfulLoginServiceTest extends AbstractSpringIntegrationIndependentTest {
29+
30+
private static final String TEST_PREFIX = "arsucloginst";
31+
32+
@Autowired
33+
private ArtemisSuccessfulLoginService artemisSuccessfulLoginService;
34+
35+
@BeforeEach
36+
void init() {
37+
userUtilService.addUsers(TEST_PREFIX, 1, 2, 0, 1);
38+
}
39+
40+
@Test
41+
@WithMockUser(username = TEST_PREFIX + "student1", roles = "INSTRUCTOR")
42+
void shouldSendEmailToUserOnSuccessfulAuthentication() throws EntityNotFoundException {
43+
String username = TEST_PREFIX + "student1";
44+
45+
User user = userTestRepository.findOneByLogin(username).get();
46+
47+
doNothing().when(javaMailSender).send(any(MimeMessage.class));
48+
artemisSuccessfulLoginService.sendLoginEmail(username);
49+
await().atMost(5, SECONDS)
50+
.untilAsserted(() -> verify(mailSendingService).buildAndSendAsync(eq(user), eq("email.notification.login.title"), eq("mail/notification/newLoginEmail"), anyMap()));
51+
}
52+
53+
@Test
54+
@WithMockUser(username = TEST_PREFIX + "student1", roles = "INSTRUCTOR")
55+
void shouldHandleUserNotFoundGracefully() throws EntityNotFoundException {
56+
String username = "nonexistentuser";
57+
doNothing().when(javaMailSender).send(any(MimeMessage.class));
58+
59+
artemisSuccessfulLoginService.sendLoginEmail(username);
60+
61+
verify(mailSendingService, never()).buildAndSendAsync(any(User.class), anyString(), anyString(), anyMap());
62+
}
63+
}

src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractArtemisIntegrationTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import de.tum.cit.aet.artemis.assessment.test_repository.ResultTestRepository;
3434
import de.tum.cit.aet.artemis.communication.service.WebsocketMessagingService;
3535
import de.tum.cit.aet.artemis.communication.service.notifications.GroupNotificationService;
36+
import de.tum.cit.aet.artemis.communication.service.notifications.MailSendingService;
3637
import de.tum.cit.aet.artemis.communication.service.notifications.MailService;
3738
import de.tum.cit.aet.artemis.communication.service.notifications.SingleUserNotificationService;
3839
import de.tum.cit.aet.artemis.communication.service.notifications.push_notifications.ApplePushNotificationService;
@@ -110,6 +111,9 @@ public abstract class AbstractArtemisIntegrationTest implements MockDelegate {
110111
@MockitoSpyBean
111112
protected MailService mailService;
112113

114+
@MockitoSpyBean
115+
protected MailSendingService mailSendingService;
116+
113117
@MockitoSpyBean
114118
protected FirebasePushNotificationService firebasePushNotificationService;
115119

0 commit comments

Comments
 (0)