java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/LandscapeManagementPanel.java
... ...
@@ -1561,7 +1561,7 @@ public class LandscapeManagementPanel extends SimplePanel {
1561 1561
new AsyncCallback<Void>() {
1562 1562
@Override
1563 1563
public void onSuccess(Void result) {
1564
- Notification.notify(stringMessages.success(), NotificationType.SUCCESS);
1564
+ Notification.notify(stringMessages.unlockedSuccessfully(), NotificationType.SUCCESS);
1565 1565
proxiesTableBusy.setBusy(false);
1566 1566
refreshProxiesTable();
1567 1567
}
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/i18n/StringMessages.java
... ...
@@ -172,7 +172,7 @@ com.sap.sse.gwt.adminconsole.StringMessages {
172 172
String successfullyRotatedHttpdLogsOnInstance(String instance);
173 173
String invalidOperationForThisProxy();
174 174
String pleaseProvideNonEmptyNameAndAZ();
175
- String success();
175
+ String unlockedSuccessfully();
176 176
String availabilityZone();
177 177
String runOnExisting();
178 178
String publicIp();
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/usermanagement/UserManagementPanelPO.java
... ...
@@ -30,7 +30,10 @@ public class UserManagementPanelPO extends PageArea {
30 30
private WebElement createUserButton;
31 31
32 32
@FindBy(how = BySeleniumId.class, using = "DeleteUserButton")
33
- private WebElement deleteUserButton;
33
+ private WebElement deleteUserButton;
34
+
35
+ @FindBy(how = BySeleniumId.class, using = "UnlockUserButton")
36
+ private WebElement unlockUserButton;
34 37
35 38
@FindBy(how = BySeleniumId.class, using = "UserNameTextbox")
36 39
private WebElement userNameTextbox;
... ...
@@ -151,6 +154,31 @@ public class UserManagementPanelPO extends PageArea {
151 154
public void deleteSelectedUser() {
152 155
deleteUserButton.click();
153 156
}
157
+
158
+ public void unlockSelectedUsers() {
159
+ final List<DataEntryPO> selection = getUserTable().getSelectedEntries();
160
+ final List<String> selectedUsersNames = new ArrayList<String>();
161
+ for (DataEntryPO dataEntryPO : selection) {
162
+ final String username = dataEntryPO.getColumnContent("User name");
163
+ selectedUsersNames.add(username);
164
+ }
165
+ unlockUserButton.click();
166
+ // confirmation dialog
167
+ waitForAlertAndAccept();
168
+ // response dialog
169
+ waitForAlertAndAccept();
170
+ // wait until all locked untils of previously selected then unlocked users are blanked out
171
+ waitUntil(() -> {
172
+ for (String selectedUsersName : selectedUsersNames) {
173
+ final String lockedUntilVal = findUser(selectedUsersName).getColumnContent("Locked until");
174
+ if (!lockedUntilVal.equals("")) {
175
+ return false;
176
+ }
177
+ }
178
+ return true;
179
+ });
180
+
181
+ }
154 182
155 183
public void unlockUser(String name) {
156 184
selectUser(name);
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/usermanagement/TestUserManagement.java
... ...
@@ -123,6 +123,27 @@ public class TestUserManagement extends AbstractSeleniumTest {
123 123
TEST_USER_NAME, TEST_USER_PASSWORD + UserManagementPanelPO.PASSWORD_COMPLEXITY_SALT));
124 124
}
125 125
126
+ @SeleniumTestCase
127
+ public void testUnlockSelectionOfUsers() throws InterruptedException {
128
+ // at admin
129
+ UserManagementPanelPO userManagementPanel = goToUserManagementPanel();
130
+ createUser(userManagementPanel);
131
+ // logout so test user can login
132
+ AuthenticationMenuPO authenticationMenu = logoutAndGoToAdminConsolePage().getAuthenticationMenu();
133
+ attemptAbusiveLogins(TEST_USER_NAME, "wrongPassword", 5, authenticationMenu);
134
+ // 16s lock window in place now, sufficient to log into admin, unlock user
135
+ // and return till test user can login, within the erstwhile lock window
136
+ assertTrue(authenticationMenu.attemptLogin("admin", "admin"));
137
+ userManagementPanel = goToUserManagementPanel();
138
+ userManagementPanel.selectUser(TEST_USER_NAME);
139
+ userManagementPanel.unlockSelectedUsers();
140
+ // logout and correct login within now-unlocked lock window
141
+ authenticationMenu = logoutAndGoToAdminConsolePage().getAuthenticationMenu();
142
+ assertTrue(
143
+ authenticationMenu.attemptLogin(
144
+ TEST_USER_NAME, TEST_USER_PASSWORD + UserManagementPanelPO.PASSWORD_COMPLEXITY_SALT));
145
+ }
146
+
126 147
private void attemptAbusiveLogins(final String username, final String wrongPassword, final int attempts, AuthenticationMenuPO authenticationMenu)
127 148
throws InterruptedException {
128 149
// logout so test user can login
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementWriteService.java
... ...
@@ -78,9 +78,12 @@ public interface UserManagementWriteService extends UserManagementService {
78 78
throws UserManagementException, org.apache.shiro.authz.UnauthorizedException;
79 79
80 80
SuccessInfo deleteUser(String username) throws UnauthorizedException, org.apache.shiro.authz.UnauthorizedException;
81
-
81
+
82 82
SuccessInfo unlockUser(String username) throws UnauthorizedException, org.apache.shiro.authz.UnauthorizedException;
83 83
84
+ Set<SuccessInfo> unlockUsers(Set<String> usernames)
85
+ throws UnauthorizedException, org.apache.shiro.authz.UnauthorizedException;
86
+
84 87
Set<SuccessInfo> deleteUsers(Set<String> usernames)
85 88
throws UnauthorizedException, org.apache.shiro.authz.UnauthorizedException;
86 89
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementWriteServiceAsync.java
... ...
@@ -63,6 +63,8 @@ public interface UserManagementWriteServiceAsync extends UserManagementServiceAs
63 63
void deleteUser(String username, AsyncCallback<SuccessInfo> callback);
64 64
65 65
void unlockUser(String username, AsyncCallback<SuccessInfo> callback);
66
+
67
+ void unlockUsers(Set<String> username, AsyncCallback<Set<SuccessInfo>> callback);
66 68
67 69
void deleteUsers(Set<String> usernames, AsyncCallback<Set<SuccessInfo>> callback);
68 70
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/AccessControlledButtonPanel.java
... ...
@@ -179,6 +179,28 @@ public class AccessControlledButtonPanel extends Composite {
179 179
return resolveButtonVisibility(permissionCheck, new Button(text, wrap(permissionCheck, callback)));
180 180
}
181 181
182
+ /**
183
+ * Adds an action button, appended with selection count, whose visibility depends on the provided {@link Supplier
184
+ * permission check}.
185
+ *
186
+ * @param text
187
+ * the {@link String text} to show on the button
188
+ * @param permissionCheck
189
+ * the {@link Supplier permission check} to decide if the action button is visible or not
190
+ * @param callback
191
+ * the {@link Command callback} to execute on button click, if permission is granted
192
+ * @return the created {@link Button} instance
193
+ */
194
+ public <T extends Named> Button addCountingAction(final String text, final SetSelectionModel<T> selectionModel,
195
+ final Supplier<Boolean> permissionCheck, final Command callback) {
196
+ if (selectionModel == null) {
197
+ throw new IllegalArgumentException("Selection model for a remove action must not be null");
198
+ }
199
+ final SelectedElementsCountingButton<T> button = new SelectedElementsCountingButton<T>(text,
200
+ selectionModel, wrap(permissionCheck, callback));
201
+ return resolveButtonVisibility(permissionCheck, button);
202
+ }
203
+
182 204
private Button resolveButtonVisibility(final Supplier<Boolean> permissionCheck, final Button button) {
183 205
this.buttonToPermissions.put(button, permissionCheck);
184 206
button.getElement().getStyle().setMarginRight(5, Unit.PX);
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java
... ...
@@ -151,8 +151,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
151 151
String group();
152 152
String errorDeletingUser(String username, String message);
153 153
String doYouReallyWantToUnlockUser(String name);
154
- String unlockSucceededFor(String username);
155
- String unlockFailedFor(String username);
154
+ String doYouReallyWantToUnlockNUsers(int n);
155
+ String unlockSucceededForUser(String username);
156
+ String unlockFailedForUser(String username);
156 157
String userIsAlreadyUnlocked();
157 158
String errorTryingToUpdateUser(String username, String message);
158 159
String ownership();
... ...
@@ -230,4 +231,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
230 231
String lockedUntil();
231 232
String passwordAuthenticationCurrentlyLockedForUser();
232 233
String clientCurrentlyLockedForUserCreation();
234
+ String unlockedSuccessfully();
235
+ String failedToUnlock();
233 236
}
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties
... ...
@@ -156,10 +156,10 @@ validated=Validated
156 156
groups=Groups
157 157
group=Group
158 158
errorDeletingUser=Error deleting user {0}: {1}
159
-doYouReallyWantToRemoveUser=Really remove user {0}?
160 159
doYouReallyWantToUnlockUser=Really allow locked user {0} to access SAP Sailing Analytics again?
161
-unlockSucceededFor=Unlock succeeded for user {0}.
162
-unlockFailedFor=Unlock failed for user {0}.
160
+doYouReallyWantToUnlockNUsers=Really allow {0} users to access SAP Sailing Analytics again?
161
+unlockSucceededForUser=Unlock succeeded for user {0}.
162
+unlockFailedForUser=Unlock failed for user {0}.
163 163
userIsAlreadyUnlocked=User is already unlocked.
164 164
errorTryingToUpdateUser=Error trying to update user {0}: {1}
165 165
ownership=Ownership
... ...
@@ -237,4 +237,6 @@ paymentUnfinished=Waiting for payment to process.
237 237
paymentFinished=Payment successful. Plan roles have been granted.
238 238
lockedUntil=Locked until
239 239
passwordAuthenticationCurrentlyLockedForUser=Password authentication is currently locked for this user due to too many failed login attempts. Please try again later.
240
-clientCurrentlyLockedForUserCreation=Client is currently locked for user creation after too many user creations by the same client. Please try again later.
... ...
\ No newline at end of file
0
+clientCurrentlyLockedForUserCreation=Client is currently locked for user creation after too many user creations by the same client. Please try again later.
1
+unlockedSuccessfully=Unlocked successfully
2
+failedToUnlock=Failed to unlock
... ...
\ No newline at end of file
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties
... ...
@@ -154,7 +154,11 @@ validated=Geprüft
154 154
groups=Gruppen
155 155
group=Gruppe
156 156
errorDeletingUser=Fehler beim Löschen des Benutzers {0}: {1}
157
-doYouReallyWantToRemoveUser=Benutzer {0} wirklich löschen?
157
+doYouReallyWantToUnlockUser=Soll dem gesperrten Benutzer {0} der Zugriff auf SAP Sailing Analytics wirklich wieder gewährt werden?
158
+doYouReallyWantToUnlockNUsers=Soll {0} Benutzern wirklich wieder Zugriff auf SAP Sailing Analytics gewährt werden?
159
+unlockSucceededForUser=Entsperrung für Benutzer {0} erfolgreich.
160
+unlockFailedForUser=Entsperrung für Benutzer {0} fehlgeschlagen.
161
+userIsAlreadyUnlocked=Der Benutzer ist bereits entsperrt.
158 162
errorTryingToUpdateUser=Fehler beim Aktualisieren des Benutzers {0}: {1}
159 163
ownership=Besitzer
160 164
editObjectOwnership=Objekt-Besitzer bearbeiten
... ...
@@ -228,4 +232,6 @@ paymentUnfinished=Warte auf Zahlungsbestätigung.
228 232
paymentFinished=Zahlung erfolgreich. Planrollen wurden verliehen.
229 233
lockedUntil=Gesperrt bis
230 234
passwordAuthenticationCurrentlyLockedForUser=Passwort-Authentifizierung ist für diesen Benutzer derzeit aufgrund zu vieler fehlgeschlagener Versuche gesperrt. Bitter später erneut versuchen.
231
-clientCurrentlyLockedForUserCreation=Benutzererstellung für den aktuellen Client ist derzeit wegen zu vieler neuer Nutzer vom selben Client gesperrt. Bitte später erneut versuchen.
... ...
\ No newline at end of file
0
+clientCurrentlyLockedForUserCreation=Benutzererstellung für den aktuellen Client ist derzeit wegen zu vieler neuer Nutzer vom selben Client gesperrt. Bitte später erneut versuchen.
1
+unlockedSuccessfully=Erfolgreich entsperrt.
2
+failedToUnlock=Entsperren fehlgeschlagen
... ...
\ No newline at end of file
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserManagementPanel.java
... ...
@@ -26,6 +26,7 @@ import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel;
26 26
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
27 27
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
28 28
import com.sap.sse.security.shared.dto.UserDTO;
29
+import com.sap.sse.security.shared.impl.SecuredSecurityTypes.UserActions;
29 30
import com.sap.sse.security.ui.client.UserManagementWriteServiceAsync;
30 31
import com.sap.sse.security.ui.client.UserService;
31 32
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
... ...
@@ -102,6 +103,12 @@ public class UserManagementPanel<TR extends CellTableWithCheckboxResources> exte
102 103
}
103 104
});
104 105
removeButton.ensureDebugId("DeleteUserButton");
106
+ final boolean hasManageLockPermissionOnAnyUser = userService.hasCurrentUserAnyPermission(USER.getPermission(UserActions.MANAGE_LOCK), null);
107
+ if (hasManageLockPermissionOnAnyUser) {
108
+ final Button unlockButton = buttonPanel.addCountingAction("Unlock", userSelectionModel, () -> true,
109
+ () -> onCountingUnlockButtonClick(stringMessages, userManagementWriteService, errorReporter));
110
+ unlockButton.ensureDebugId("UnlockUserButton");
111
+ }
105 112
ScrollPanel scrollPanel = new ScrollPanel(userList.asWidget());
106 113
LabeledAbstractFilterablePanel<UserDTO> filterBox = userList.getFilterField();
107 114
filterBox.getElement().setPropertyString("placeholder", stringMessages.filterUsers());
... ...
@@ -137,6 +144,67 @@ public class UserManagementPanel<TR extends CellTableWithCheckboxResources> exte
137 144
west.add(detailsPanel);
138 145
}
139 146
147
+ private void onCountingUnlockButtonClick(final StringMessages stringMessages,
148
+ final UserManagementWriteServiceAsync userManagementWriteService, final ErrorReporter errorReporter) {
149
+ final Set<UserDTO> selectedUsers = userSelectionModel.getSelectedSet();
150
+ final Set<String> selectedUsernames = new HashSet<String>();
151
+ selectedUsers.forEach((u) -> selectedUsernames.add(u.getName()));
152
+ final boolean didConfirm = Window.confirm(stringMessages.doYouReallyWantToUnlockNUsers(selectedUsers.size()));
153
+ if (didConfirm) {
154
+ // run api, collect results
155
+ final Set<String> successUserNames = new HashSet<String>();
156
+ final Set<String> failureUserNames = new HashSet<String>();
157
+ userManagementWriteService.unlockUsers(selectedUsernames, new AsyncCallback<Set<SuccessInfo>>() {
158
+ @Override
159
+ public void onSuccess(Set<SuccessInfo> result) {
160
+ for (SuccessInfo si : result) {
161
+ final String username = si.getExtra();
162
+ if (si.isSuccessful()) {
163
+ successUserNames.add(username);
164
+ } else {
165
+ failureUserNames.add(username);
166
+ }
167
+ }
168
+ // update locked until entries for successful unlocks
169
+ final LabeledAbstractFilterablePanel<UserDTO> filterField = userList.getFilterField();
170
+ final List<UserDTO> allUsersWithUpdatedEntries = new ArrayList<UserDTO>();
171
+ for (UserDTO user : filterField.getAll()) {
172
+ final boolean didSucceed = successUserNames.contains(user.getName());
173
+ if (didSucceed) {
174
+ allUsersWithUpdatedEntries.add(user.copyWithTimePoint(null));
175
+ } else {
176
+ allUsersWithUpdatedEntries.add(user);
177
+ }
178
+ }
179
+ filterField.updateAll(allUsersWithUpdatedEntries);
180
+ // create alert dialog
181
+ String text = "";
182
+ if (successUserNames.size() != 0) {
183
+ text += stringMessages.unlockedSuccessfully() + ":\n";
184
+ for (String name : successUserNames) {
185
+ text += " • " + name + "\n";
186
+ }
187
+ text += "\n";
188
+ }
189
+ if (failureUserNames.size() != 0) {
190
+ text += stringMessages.failedToUnlock() + ":\n";
191
+ for (String name : failureUserNames) {
192
+ text += " • " + name + "\n";
193
+ }
194
+ }
195
+ if(!text.equals("")) {
196
+ Window.alert(text);
197
+ }
198
+ }
199
+
200
+ @Override
201
+ public void onFailure(Throwable caught) {
202
+ errorReporter.reportError(stringMessages.failedToUnlock() + ": " + caught.getMessage());
203
+ }
204
+ });
205
+ }
206
+ }
207
+
140 208
/** shows the edit dialog if the user exists */
141 209
private void showRolesAndPermissionsEditDialog(UserService userService,
142 210
final CellTableWithCheckboxResources tableResources, final ErrorReporter errorReporter) {
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java
... ...
@@ -259,21 +259,21 @@ extends TableWrapper<UserDTO, S, StringMessages, TR> {
259 259
getUserManagementWriteService().unlockUser(userName, new AsyncCallback<SuccessInfo>() {
260 260
@Override
261 261
public void onSuccess(SuccessInfo result) {
262
- Window.alert(stringMessages.unlockSucceededFor(userName));
263
- final List<UserDTO> usersWithUpdate = new ArrayList<UserDTO>();
262
+ Window.alert(stringMessages.unlockSucceededForUser(userName));
263
+ final List<UserDTO> usersWithUpdatedEntry = new ArrayList<UserDTO>();
264 264
for (UserDTO user : getAllUsers()) {
265 265
if (user.getFullName() == selectedUser.getFullName()) {
266
- usersWithUpdate.add(user.copyWithTimePoint(null));
266
+ usersWithUpdatedEntry.add(user.copyWithTimePoint(null));
267 267
} else {
268
- usersWithUpdate.add(user);
268
+ usersWithUpdatedEntry.add(user);
269 269
}
270 270
}
271
- filterField.updateAll(usersWithUpdate);
271
+ filterField.updateAll(usersWithUpdatedEntry);
272 272
}
273 273
274 274
@Override
275 275
public void onFailure(Throwable caught) {
276
- Window.alert(stringMessages.unlockFailedFor(userName));
276
+ Window.alert(stringMessages.unlockFailedForUser(userName));
277 277
errorReporter.reportError(caught.getMessage());
278 278
}
279 279
});
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementWriteServiceImpl.java
... ...
@@ -382,20 +382,29 @@ public class UserManagementWriteServiceImpl extends UserManagementServiceImpl im
382 382
if (!getSecurityService().hasCurrentUserExplicitPermissions(user, UserActions.MANAGE_LOCK)) {
383 383
logger.info("You are not permitted to manage locking on user " + username);
384 384
return new SuccessInfo(false, "You are not permitted to manage locking on user " + username,
385
- /* redirectURL */ null, null);
385
+ /* redirectURL */ null, null, username);
386 386
}
387 387
try {
388 388
getSecurityService().resetUserTimedLock(username);
389 389
logger.info("Reset lock on user: " + username + ".");
390
- return new SuccessInfo(true, "Reset lock on user: " + username + ".", /* redirectURL */ null, null);
390
+ return new SuccessInfo(true, "Reset lock on user: " + username + ".", /* redirectURL */ null, null, username);
391 391
} catch (UserManagementException e) {
392 392
logger.info("Could not reset lock on user: " + username + ".");
393
- return new SuccessInfo(false, "Could not reset lock on user " + username, /* redirectURL */ null, null);
393
+ return new SuccessInfo(false, "Could not reset lock on user " + username, /* redirectURL */ null, null, username);
394 394
}
395 395
} else {
396 396
logger.info("Could not reset lock on user: " + username + ".");
397
- return new SuccessInfo(false, "Could not reset lock on user " + username, /* redirectURL */ null, null);
397
+ return new SuccessInfo(false, "Could not reset lock on user " + username, /* redirectURL */ null, null, username);
398
+ }
399
+ }
400
+
401
+ @Override
402
+ public Set<SuccessInfo> unlockUsers(Set<String> usernames) throws UnauthorizedException {
403
+ final Set<SuccessInfo> result = new HashSet<>();
404
+ for (String username : usernames) {
405
+ result.add(unlockUser(username));
398 406
}
407
+ return result;
399 408
}
400 409
401 410
@Override
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/SuccessInfo.java
... ...
@@ -15,6 +15,7 @@ public class SuccessInfo implements IsSerializable {
15 15
private String message;
16 16
private Triple<UserDTO, UserDTO, ServerInfoDTO> userDTO;
17 17
private String redirectURL;
18
+ private String extra;
18 19
19 20
SuccessInfo() {} // for serializtion only
20 21
... ...
@@ -25,6 +26,15 @@ public class SuccessInfo implements IsSerializable {
25 26
this.redirectURL = redirectURL;
26 27
this.userDTO = userDTO;
27 28
}
29
+
30
+ public SuccessInfo(boolean successful, String message, String redirectURL, Triple<UserDTO, UserDTO, ServerInfoDTO> userDTO, String extra) {
31
+ super();
32
+ this.successful = successful;
33
+ this.message = message;
34
+ this.redirectURL = redirectURL;
35
+ this.userDTO = userDTO;
36
+ this.extra = extra;
37
+ }
28 38
29 39
public boolean isSuccessful() {
30 40
return successful;
... ...
@@ -37,9 +47,14 @@ public class SuccessInfo implements IsSerializable {
37 47
public Triple<UserDTO, UserDTO, ServerInfoDTO> getUserDTO() {
38 48
return userDTO;
39 49
}
40
-
50
+
41 51
public String getRedirectURL() {
42 52
return redirectURL;
43 53
}
44 54
55
+
56
+ public String getExtra() {
57
+ return extra;
58
+ }
59
+
45 60
}
java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
... ...
@@ -1326,7 +1326,7 @@ implements ReplicableSecurityService, ClearStateTestSupport {
1326 1326
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
1327 1327
}
1328 1328
if (user.getTimedLock().isLocked()) {
1329
- throw new UserManagementException("Password authentication is locked for user "+username);
1329
+ throw new UserManagementException(UserManagementException.PASSWORD_AUTHENTICATION_CURRENTLY_LOCKED_FOR_USER);
1330 1330
}
1331 1331
final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
1332 1332
String hashedOldPassword = hashPassword(password, account.getSalt());