|
7 | 7 | import java.util.Optional; |
8 | 8 | import java.util.stream.Collectors; |
9 | 9 | import org.springframework.beans.factory.annotation.Value; |
| 10 | +import org.springframework.context.ApplicationEventPublisher; |
10 | 11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
11 | 12 | import org.springframework.security.core.Authentication; |
12 | 13 | import org.springframework.security.core.GrantedAuthority; |
|
19 | 20 | import org.springframework.web.context.request.RequestContextHolder; |
20 | 21 | import org.springframework.web.context.request.ServletRequestAttributes; |
21 | 22 | import com.digitalsanctuary.spring.user.dto.UserDto; |
| 23 | +import com.digitalsanctuary.spring.user.event.UserPreDeleteEvent; |
22 | 24 | import com.digitalsanctuary.spring.user.exceptions.UserAlreadyExistException; |
23 | 25 | import com.digitalsanctuary.spring.user.persistence.model.PasswordResetToken; |
24 | 26 | import com.digitalsanctuary.spring.user.persistence.model.User; |
|
76 | 78 | * <ul> |
77 | 79 | * <li>{@link #registerNewUserAccount(UserDto)}: Registers a new user account.</li> |
78 | 80 | * <li>{@link #saveRegisteredUser(User)}: Saves a registered user.</li> |
79 | | - * <li>{@link #deleteUser(User)}: Deletes a user and cleans up associated tokens.</li> |
| 81 | + * <li>{@link #deleteOrDisableUser(User)}: Deletes a user and cleans up associated tokens.</li> |
80 | 82 | * <li>{@link #findUserByEmail(String)}: Finds a user by email.</li> |
81 | 83 | * <li>{@link #getPasswordResetToken(String)}: Gets a password reset token by token string.</li> |
82 | 84 | * <li>{@link #getUserByPasswordResetToken(String)}: Gets a user by password reset token.</li> |
@@ -191,14 +193,18 @@ public String getValue() { |
191 | 193 | /** The user details service. */ |
192 | 194 | private final DSUserDetailsService dsUserDetailsService; |
193 | 195 |
|
| 196 | + private final ApplicationEventPublisher eventPublisher; |
| 197 | + |
194 | 198 | /** The send registration verification email flag. */ |
195 | 199 | @Value("${user.registration.sendVerificationEmail:false}") |
196 | 200 | private boolean sendRegistrationVerificationEmail; |
197 | 201 |
|
| 202 | + @Value("${user.actuallyDeleteAccount:false}") |
| 203 | + private boolean actuallyDeleteAccount; |
| 204 | + |
198 | 205 | /** |
199 | | - * Registers a new user account with the provided user data. |
200 | | - * If the email already exists, throws a UserAlreadyExistException. |
201 | | - * If sendRegistrationVerificationEmail is false, the user is enabled immediately. |
| 206 | + * Registers a new user account with the provided user data. If the email already exists, throws a UserAlreadyExistException. If |
| 207 | + * sendRegistrationVerificationEmail is false, the user is enabled immediately. |
202 | 208 | * |
203 | 209 | * @param newUserDto the data transfer object containing the user registration information |
204 | 210 | * @return the newly created user entity |
@@ -243,25 +249,45 @@ public User saveRegisteredUser(final User user) { |
243 | 249 | } |
244 | 250 |
|
245 | 251 | /** |
246 | | - * Delete user. |
| 252 | + * Delete user and clean up associated tokens. If actuallyDeleteAccount is true, the user is deleted from the database. Otherwise, the user is |
| 253 | + * disabled. |
247 | 254 | * |
248 | | - * @param user the user |
| 255 | + * Transactional method to ensure that the operation is atomic. If any part of the operation fails, the entire transaction is rolled back. This |
| 256 | + * includes the Event to allow the consuming application to handle data cleanup as needed before the User is deleted. |
| 257 | + * |
| 258 | + * @param user the user to delete or disable |
249 | 259 | */ |
250 | | - public void deleteUser(final User user) { |
251 | | - // Clean up any Tokens associated with this user |
252 | | - final VerificationToken verificationToken = tokenRepository.findByUser(user); |
253 | | - if (verificationToken != null) { |
254 | | - tokenRepository.delete(verificationToken); |
255 | | - } |
| 260 | + @Transactional |
| 261 | + public void deleteOrDisableUser(final User user) { |
| 262 | + log.debug("UserService.deleteOrDisableUser: called with user: {}", user); |
| 263 | + if (actuallyDeleteAccount) { |
| 264 | + log.debug("UserService.deleteOrDisableUser: actuallyDeleteAccount is true, deleting user: {}", user); |
| 265 | + // Publish the UserPreDeleteEvent before deleting the user |
| 266 | + // This allows any listeners to perform actions before the user is deleted |
| 267 | + log.debug("Publishing UserPreDeleteEvent"); |
| 268 | + eventPublisher.publishEvent(new UserPreDeleteEvent(this, user)); |
| 269 | + |
| 270 | + // Clean up any Tokens associated with this user |
| 271 | + final VerificationToken verificationToken = tokenRepository.findByUser(user); |
| 272 | + if (verificationToken != null) { |
| 273 | + tokenRepository.delete(verificationToken); |
| 274 | + } |
256 | 275 |
|
257 | | - final PasswordResetToken passwordToken = passwordTokenRepository.findByUser(user); |
258 | | - if (passwordToken != null) { |
259 | | - passwordTokenRepository.delete(passwordToken); |
| 276 | + final PasswordResetToken passwordToken = passwordTokenRepository.findByUser(user); |
| 277 | + if (passwordToken != null) { |
| 278 | + passwordTokenRepository.delete(passwordToken); |
| 279 | + } |
| 280 | + // Delete the user |
| 281 | + userRepository.delete(user); |
| 282 | + } else { |
| 283 | + log.debug("UserService.deleteOrDisableUser: actuallyDeleteAccount is false, disabling user: {}", user); |
| 284 | + user.setEnabled(false); |
| 285 | + userRepository.save(user); |
| 286 | + log.debug("UserService.deleteOrDisableUser: user {} has been disabled", user.getEmail()); |
260 | 287 | } |
261 | | - // Delete the user |
262 | | - userRepository.delete(user); |
263 | 288 | } |
264 | 289 |
|
| 290 | + |
265 | 291 | /** |
266 | 292 | * Find user by email. |
267 | 293 | * |
@@ -371,13 +397,13 @@ public List<String> getUsersFromSessionRegistry() { |
371 | 397 | } |
372 | 398 |
|
373 | 399 | /** |
374 | | - * Authenticates the given user without requiring a password. This method loads the user's details, |
375 | | - * generates their authorities from their roles and privileges, and stores these details in the |
376 | | - * security context and session. |
377 | | - * |
378 | | - * <p><strong>SECURITY WARNING:</strong> This is a potentially dangerous method as it authenticates |
379 | | - * a user without password verification. This method should only be used in specific controlled scenarios, |
380 | | - * such as after successful email verification or OAuth authentication.</p> |
| 400 | + * Authenticates the given user without requiring a password. This method loads the user's details, generates their authorities from their roles |
| 401 | + * and privileges, and stores these details in the security context and session. |
| 402 | + * |
| 403 | + * <p> |
| 404 | + * <strong>SECURITY WARNING:</strong> This is a potentially dangerous method as it authenticates a user without password verification. This method |
| 405 | + * should only be used in specific controlled scenarios, such as after successful email verification or OAuth authentication. |
| 406 | + * </p> |
381 | 407 | * |
382 | 408 | * @param user The user to authenticate without password verification |
383 | 409 | */ |
|
0 commit comments