diff --git a/src/main/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormController.java b/src/main/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormController.java index 41a1672..7a18e71 100644 --- a/src/main/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormController.java +++ b/src/main/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormController.java @@ -108,6 +108,9 @@ private void onSave(ActionEvent e) { if (data == null) { return; } + if (isSelfDowngrade(data)) { + return; + } hideError(); setFormDisabled(true); @@ -147,6 +150,34 @@ private void onCancel(ActionEvent e) { // ===== Validation / data ===== + /** + * Админ не может понизить или заблокировать сам себя (симметрично запрету самоудаления в {@code + * UserDetailsController}): цель — текущий пользователь, роль уходит с ADMIN или активность + * снимается. Показывает ошибку и возвращает {@code true} — сохранение пропускается без вызова + * сервиса и без навигации. Свои имя/email вне скоупа и не блокируются. + */ + private boolean isSelfDowngrade(UpdateUserData data) { + var authService = services == null ? null : services.getAuthService(); + var me = authService == null ? null : authService.getCurrentUser(); + boolean isSelf = + me != null + && current != null + && current.getUuid() != null + && current.getUuid().equals(me.getUuid()); + if (!isSelf) { + return false; + } + if (current.getRole() == Role.ADMIN && data.getRole() != Role.ADMIN) { + showError("Нельзя снять себе роль администратора"); + return true; + } + if (Boolean.FALSE.equals(data.getEnabled())) { + showError("Нельзя отключить собственную учётную запись"); + return true; + } + return false; + } + /** * Собирает данные формы. Если валидация не прошла — показывает ошибку и возвращает {@code null}. */ diff --git a/src/test/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormControllerTest.java b/src/test/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormControllerTest.java index 3e0ce99..88edd59 100644 --- a/src/test/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormControllerTest.java +++ b/src/test/java/com/DesktopApplicationClientJava/controllers/UserUpdateFormControllerTest.java @@ -19,6 +19,7 @@ import com.DesktopApplicationClientJava.entities.Role; import com.DesktopApplicationClientJava.entities.User; import com.DesktopApplicationClientJava.navigation.Navigator; +import com.DesktopApplicationClientJava.services.AuthService; import com.DesktopApplicationClientJava.services.ServiceException; import com.DesktopApplicationClientJava.services.ServiceRegistry; import com.DesktopApplicationClientJava.services.UsersService; @@ -64,6 +65,7 @@ class UserUpdateFormControllerTest { private static final String LAST_NAME = "Петров"; private UsersService usersService; + private AuthService authService; private Navigator navigator; private UserUpdateFormController controller; @@ -90,10 +92,13 @@ static void bootstrapFx() { @BeforeEach void setUp() { usersService = mock(UsersService.class); + authService = mock(AuthService.class); navigator = mock(Navigator.class); ServiceRegistry services = mock(ServiceRegistry.class); when(services.getUsersService()).thenReturn(usersService); + when(services.getAuthService()).thenReturn(authService); + when(authService.getCurrentUser()).thenReturn(null); controller = new UserUpdateFormController(); ControllerWiring.wire(controller, navigator, new Session(), services); @@ -604,6 +609,84 @@ void saveNetworkErrorShowsErrorAndReenables() { assertFalse(emailField.isDisabled()); } + // ===== Самодаунгрейд и самоблокировка (#95) ===== + + @Test + @DisplayName("Своя запись: смена роли с ADMIN на другую блокируется") + void selfRoleDowngradeIsBlocked() { + User me = new User(USER_ID, EMAIL, Role.ADMIN, FIRST_NAME, LAST_NAME, true); + when(authService.getCurrentUser()).thenReturn(me); + when(usersService.getById(USER_ID)).thenReturn(me); + controller.setUserId(USER_ID); + awaitLoaded(); + + roleChoiceBox.getSelectionModel().select(Role.USER); + invokeHandler("onSave"); + + assertTrue(errorLabel.isVisible()); + assertEquals("Нельзя снять себе роль администратора", errorLabel.getText()); + verify(usersService, never()).update(any(), any()); + verify(navigator, never()).goTo(anyString()); + } + + @Test + @DisplayName("Своя запись: снятие активности себе блокируется") + void selfDisableIsBlocked() { + User me = new User(USER_ID, EMAIL, Role.ADMIN, FIRST_NAME, LAST_NAME, true); + when(authService.getCurrentUser()).thenReturn(me); + when(usersService.getById(USER_ID)).thenReturn(me); + controller.setUserId(USER_ID); + awaitLoaded(); + + enabledCheckBox.setSelected(false); + invokeHandler("onSave"); + + assertTrue(errorLabel.isVisible()); + assertEquals("Нельзя отключить собственную учётную запись", errorLabel.getText()); + verify(usersService, never()).update(any(), any()); + verify(navigator, never()).goTo(anyString()); + } + + @Test + @DisplayName("Чужой пользователь: смена роли и активности не блокируется") + void otherUserRoleAndEnabledChangeIsNotBlocked() { + User me = + new User(UUID.randomUUID(), "admin@example.com", Role.ADMIN, "Админ", "Главный", true); + when(authService.getCurrentUser()).thenReturn(me); + when(usersService.getById(USER_ID)).thenReturn(existingUser()); + when(usersService.update(eq(USER_ID), any())).thenReturn(existingUser()); + controller.setUserId(USER_ID); + awaitLoaded(); + + roleChoiceBox.getSelectionModel().select(Role.USER); + enabledCheckBox.setSelected(false); + invokeHandler("onSave"); + + awaitNavigation(); + verify(usersService).update(eq(USER_ID), any(UpdateUserData.class)); + } + + @Test + @DisplayName("Своя запись: смена имени и почты не блокируется") + void selfNameAndEmailChangeIsNotBlocked() { + User me = new User(USER_ID, EMAIL, Role.ADMIN, FIRST_NAME, LAST_NAME, true); + when(authService.getCurrentUser()).thenReturn(me); + when(usersService.getById(USER_ID)).thenReturn(me); + when(usersService.update(eq(USER_ID), any())).thenReturn(me); + controller.setUserId(USER_ID); + awaitLoaded(); + + firstNameField.setText("Пётр"); + emailField.setText("petr@example.com"); + invokeHandler("onSave"); + + awaitNavigation(); + ArgumentCaptor captor = ArgumentCaptor.forClass(UpdateUserData.class); + verify(usersService).update(eq(USER_ID), captor.capture()); + assertEquals("Пётр", captor.getValue().getFirstName()); + assertEquals("petr@example.com", captor.getValue().getEmail()); + } + // ===== Отмена ===== @Test