879b360d71131af52cd6d42a6405370d56a3a223
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SecuredDomainType.java
| ... | ... | @@ -52,6 +52,25 @@ public class SecuredDomainType extends HasPermissionsImpl { |
| 52 | 52 | public static final HasPermissions TRACKED_RACE = new SecuredDomainType("TRACKED_RACE", |
| 53 | 53 | TrackedRaceActions.ALL_ACTIONS); |
| 54 | 54 | |
| 55 | + public static final HasPermissions IP_BLOCKLIST_FOR_BEARER_TOKEN_ABUSE = new SecuredDomainType( |
|
| 56 | + "IP_BLOCKLIST_FOR_BEARER_TOKEN_ABUSE", IpBlocklistForBearerTokenAbuseActions.ALL_ACTIONS); |
|
| 57 | + |
|
| 58 | + public static final HasPermissions IP_BLOCKLIST_FOR_USER_CREATION_ABUSE = new SecuredDomainType( |
|
| 59 | + "IP_BLOCKLIST_FOR_USER_CREATION_ABUSE", IpBlocklistForUserCreationAbuseActions.ALL_ACTIONS); |
|
| 60 | + |
|
| 61 | + public static enum IpBlocklistForBearerTokenAbuseActions implements Action { |
|
| 62 | + GET, UNLOCK; |
|
| 63 | + |
|
| 64 | + private static final Action[] ALL_ACTIONS = DefaultActions.plus(IpBlocklistForBearerTokenAbuseActions.values()); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + |
|
| 68 | + public static enum IpBlocklistForUserCreationAbuseActions implements Action { |
|
| 69 | + GET, UNLOCK; |
|
| 70 | + |
|
| 71 | + private static final Action[] ALL_ACTIONS = DefaultActions.plus(IpBlocklistForUserCreationAbuseActions.values()); |
|
| 72 | + } |
|
| 73 | + |
|
| 55 | 74 | public static enum EventActions implements Action { |
| 56 | 75 | UPLOAD_MEDIA |
| 57 | 76 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IPBlocklistTableWrapper.java
| ... | ... | @@ -0,0 +1,151 @@ |
| 1 | +package com.sap.sailing.gwt.ui.adminconsole; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.Comparator; |
|
| 5 | +import java.util.HashMap; |
|
| 6 | +import java.util.List; |
|
| 7 | +import java.util.Map.Entry; |
|
| 8 | + |
|
| 9 | +import com.google.gwt.user.cellview.client.AbstractCellTable; |
|
| 10 | +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; |
|
| 11 | +import com.google.gwt.user.client.Command; |
|
| 12 | +import com.google.gwt.user.client.rpc.AsyncCallback; |
|
| 13 | +import com.google.gwt.user.client.ui.Button; |
|
| 14 | +import com.google.gwt.user.client.ui.CheckBox; |
|
| 15 | +import com.google.gwt.user.client.ui.Label; |
|
| 16 | +import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; |
|
| 17 | +import com.sap.sailing.gwt.ui.client.StringMessages; |
|
| 18 | +import com.sap.sse.common.TimedLock; |
|
| 19 | +import com.sap.sse.gwt.client.ErrorReporter; |
|
| 20 | +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; |
|
| 21 | +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; |
|
| 22 | +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; |
|
| 23 | +import com.sap.sse.security.shared.HasPermissions; |
|
| 24 | +import com.sap.sse.security.ui.client.UserService; |
|
| 25 | +import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel; |
|
| 26 | + |
|
| 27 | +abstract class IPBlocklistTableWrapper |
|
| 28 | + extends TableWrapper<IpToTimedLockDTO, RefreshableSelectionModel<IpToTimedLockDTO>> { |
|
| 29 | + private final UserService userService; |
|
| 30 | + private final LabeledAbstractFilterablePanel<IpToTimedLockDTO> filterField; |
|
| 31 | + private final HasPermissions securedDomainType; |
|
| 32 | + private final String errorMessageOnDataFailureString; |
|
| 33 | + |
|
| 34 | + protected abstract void fetchData(AsyncCallback<HashMap<String, TimedLock>> callback); |
|
| 35 | + |
|
| 36 | + protected abstract void unlockIP(String ip, AsyncCallback<Void> asyncCallback); |
|
| 37 | + |
|
| 38 | + public IPBlocklistTableWrapper(final SailingServiceWriteAsync sailingServiceWrite, final UserService userService, |
|
| 39 | + final HasPermissions securedDomainType, final String errorMessageOnDataFailureString, |
|
| 40 | + final StringMessages stringMessages, final ErrorReporter errorReporter) { |
|
| 41 | + super(sailingServiceWrite, stringMessages, errorReporter, true, true, |
|
| 42 | + new EntityIdentityComparator<IpToTimedLockDTO>() { |
|
| 43 | + @Override |
|
| 44 | + public boolean representSameEntity(IpToTimedLockDTO dto1, IpToTimedLockDTO dto2) { |
|
| 45 | + return dto1.ip.equals(dto2.ip); |
|
| 46 | + } |
|
| 47 | + |
|
| 48 | + @Override |
|
| 49 | + public int hashCode(IpToTimedLockDTO t) { |
|
| 50 | + return t.ip.hashCode(); |
|
| 51 | + } |
|
| 52 | + }); |
|
| 53 | + this.securedDomainType = securedDomainType; |
|
| 54 | + this.userService = userService; |
|
| 55 | + this.errorMessageOnDataFailureString = errorMessageOnDataFailureString; |
|
| 56 | + this.asWidget().ensureDebugId("wrappedTable"); |
|
| 57 | + this.table.ensureDebugId("cellTable"); |
|
| 58 | + filterField = composeFilterField(); |
|
| 59 | + mainPanel.insert(filterField.asWidget(), 0); |
|
| 60 | + mainPanel.insert(composeButtonPanel(), 1); |
|
| 61 | + configureColumns(); |
|
| 62 | + loadDataAndPopulateTable(); |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + private AccessControlledButtonPanel composeButtonPanel() { |
|
| 66 | + final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService, securedDomainType); |
|
| 67 | + final Button refreshbutton = buttonPanel.addAction(getStringMessages().refresh(), () -> true, new Command() { |
|
| 68 | + @Override |
|
| 69 | + public void execute() { |
|
| 70 | + loadDataAndPopulateTable(); |
|
| 71 | + } |
|
| 72 | + }); |
|
| 73 | + refreshbutton.ensureDebugId("refreshButton"); |
|
| 74 | + final Button unlockbutton = buttonPanel.addAction(getStringMessages().unlock(), () -> true, new Command() { |
|
| 75 | + @Override |
|
| 76 | + public void execute() { |
|
| 77 | + for (IpToTimedLockDTO e : getSelectionModel().getSelectedSet()) { |
|
| 78 | + unlockIP(e.ip, new AsyncCallback<Void>() { |
|
| 79 | + @Override |
|
| 80 | + public void onFailure(Throwable caught) { |
|
| 81 | + errorReporter.reportError(errorMessageOnDataFailureString); |
|
| 82 | + } |
|
| 83 | + |
|
| 84 | + @Override |
|
| 85 | + public void onSuccess(Void result) { |
|
| 86 | + filterField.remove(e); |
|
| 87 | + } |
|
| 88 | + }); |
|
| 89 | + } |
|
| 90 | + } |
|
| 91 | + }); |
|
| 92 | + unlockbutton.ensureDebugId("unlockButton"); |
|
| 93 | + return buttonPanel; |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + private void loadDataAndPopulateTable() { |
|
| 97 | + final AsyncCallback<HashMap<String, TimedLock>> dataInitializationCallback = new AsyncCallback<HashMap<String, TimedLock>>() { |
|
| 98 | + @Override |
|
| 99 | + public void onFailure(Throwable caught) { |
|
| 100 | + errorReporter.reportError(errorMessageOnDataFailureString); |
|
| 101 | + } |
|
| 102 | + |
|
| 103 | + @Override |
|
| 104 | + public void onSuccess(HashMap<String, TimedLock> result) { |
|
| 105 | + filterField.clear(); |
|
| 106 | + clear(); |
|
| 107 | + final ArrayList<IpToTimedLockDTO> iterable = new ArrayList<IpToTimedLockDTO>(); |
|
| 108 | + for (Entry<String, TimedLock> e : result.entrySet()) { |
|
| 109 | + if (e.getValue().isLocked()) { |
|
| 110 | + iterable.add(new IpToTimedLockDTO(e.getKey(), e.getValue())); |
|
| 111 | + } |
|
| 112 | + } |
|
| 113 | + filterField.addAll(iterable); |
|
| 114 | + } |
|
| 115 | + }; |
|
| 116 | + fetchData(dataInitializationCallback); |
|
| 117 | + } |
|
| 118 | + |
|
| 119 | + private void configureColumns() { |
|
| 120 | + final ListHandler<IpToTimedLockDTO> columnListHandler = getColumnSortHandler(); |
|
| 121 | + addColumn(record -> record.ip, getStringMessages().ipAddress()); |
|
| 122 | + final Comparator<IpToTimedLockDTO> expiryComparator = (o1, o2) -> { |
|
| 123 | + return o1.timedLock.getLockedUntil().compareTo(o2.timedLock.getLockedUntil()); |
|
| 124 | + }; |
|
| 125 | + addColumn(record -> record.timedLock.getLockedUntil().toString(), getStringMessages().lockedUntil(), |
|
| 126 | + expiryComparator); |
|
| 127 | + table.addColumnSortHandler(columnListHandler); |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + private LabeledAbstractFilterablePanel<IpToTimedLockDTO> composeFilterField() { |
|
| 131 | + final LabeledAbstractFilterablePanel<IpToTimedLockDTO> filterField = new LabeledAbstractFilterablePanel<IpToTimedLockDTO>( |
|
| 132 | + new Label(getStringMessages().filterIpAddresses()), new ArrayList<>(), getDataProvider(), |
|
| 133 | + getStringMessages()) { |
|
| 134 | + @Override |
|
| 135 | + public Iterable<String> getSearchableStrings(IpToTimedLockDTO dto) { |
|
| 136 | + List<String> string = new ArrayList<String>(); |
|
| 137 | + string.add(dto.ip); |
|
| 138 | + return string; |
|
| 139 | + } |
|
| 140 | + |
|
| 141 | + @Override |
|
| 142 | + public AbstractCellTable<IpToTimedLockDTO> getCellTable() { |
|
| 143 | + return table; |
|
| 144 | + } |
|
| 145 | + }; |
|
| 146 | + final CheckBox filterCheckbox = new CheckBox(getStringMessages().filterIpAddresses()); |
|
| 147 | + filterCheckbox.addValueChangeHandler(checked -> filterField.filter()); |
|
| 148 | + registerSelectionModelOnNewDataProvider(filterField.getAllListDataProvider()); |
|
| 149 | + return filterField; |
|
| 150 | + } |
|
| 151 | +} |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IpToTimedLockDTO.java
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +package com.sap.sailing.gwt.ui.adminconsole; |
|
| 2 | + |
|
| 3 | +import com.sap.sse.common.TimedLock; |
|
| 4 | + |
|
| 5 | +public class IpToTimedLockDTO { |
|
| 6 | + public final String ip; |
|
| 7 | + public final TimedLock timedLock; |
|
| 8 | + |
|
| 9 | + public IpToTimedLockDTO(final String ip, final TimedLock timedLock) { |
|
| 10 | + this.ip = ip; |
|
| 11 | + this.timedLock = timedLock; |
|
| 12 | + } |
|
| 13 | + |
|
| 14 | +} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java
| ... | ... | @@ -5,6 +5,7 @@ import static com.sap.sse.security.shared.HasPermissions.DefaultActions.CHANGE_O |
| 5 | 5 | |
| 6 | 6 | import java.util.ArrayList; |
| 7 | 7 | import java.util.Collections; |
| 8 | +import java.util.HashMap; |
|
| 8 | 9 | import java.util.function.Consumer; |
| 9 | 10 | import java.util.function.Predicate; |
| 10 | 11 | |
| ... | ... | @@ -24,12 +25,14 @@ import com.google.gwt.user.client.ui.SimplePanel; |
| 24 | 25 | import com.google.gwt.user.client.ui.SuggestBox; |
| 25 | 26 | import com.google.gwt.user.client.ui.VerticalPanel; |
| 26 | 27 | import com.google.gwt.user.client.ui.Widget; |
| 28 | +import com.sap.sailing.domain.common.security.SecuredDomainType; |
|
| 27 | 29 | import com.sap.sailing.gwt.ui.adminconsole.places.AdminConsoleView.Presenter; |
| 28 | 30 | import com.sap.sailing.gwt.ui.adminconsole.places.advanced.UserGroupManagementPlace; |
| 29 | 31 | import com.sap.sailing.gwt.ui.adminconsole.places.advanced.UserManagementPlace; |
| 30 | 32 | import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; |
| 31 | 33 | import com.sap.sailing.gwt.ui.client.StringMessages; |
| 32 | 34 | import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO; |
| 35 | +import com.sap.sse.common.TimedLock; |
|
| 33 | 36 | import com.sap.sse.common.Util; |
| 34 | 37 | import com.sap.sse.common.Util.Pair; |
| 35 | 38 | import com.sap.sse.common.http.HttpHeaderUtil; |
| ... | ... | @@ -86,7 +89,8 @@ public class LocalServerManagementPanel extends SimplePanel { |
| 86 | 89 | mainPanel.add(this.buttonPanel = createServerActionsUi(userService)); |
| 87 | 90 | mainPanel.add(createServerInfoUI()); |
| 88 | 91 | mainPanel.add(createServerConfigurationUI()); |
| 89 | - mainPanel.add(createIPsLockedForBearerTokenAbuseUI()); |
|
| 92 | + mainPanel.add(createBearerTokenAbusePanel()); |
|
| 93 | + mainPanel.add(createUserCreationAbusePanel()); |
|
| 90 | 94 | refreshServerConfiguration(); |
| 91 | 95 | if (userService.hasServerPermission(ServerActions.CONFIGURE_CORS_FILTER)) { |
| 92 | 96 | mainPanel.add(createCORSFilterConfigurationUI()); |
| ... | ... | @@ -144,9 +148,44 @@ public class LocalServerManagementPanel extends SimplePanel { |
| 144 | 148 | return captionPanel; |
| 145 | 149 | } |
| 146 | 150 | |
| 147 | - private Widget createIPsLockedForBearerTokenAbuseUI() { |
|
| 148 | - final ServerDataCaptionPanel captionPanel = new ServerDataCaptionPanel(stringMessages.ipsLockedForBearerTokenAbuse(), 3); |
|
| 149 | - return captionPanel; |
|
| 151 | + private Widget createBearerTokenAbusePanel() { |
|
| 152 | + final ServerDataCaptionPanel panel = new ServerDataCaptionPanel(stringMessages.ipsLockedForBearerTokenAbuse(), 3); |
|
| 153 | + panel.ensureDebugId("bearerTokenAbusePanel"); |
|
| 154 | + final IPBlocklistTableWrapper table = new IPBlocklistTableWrapper(sailingService, userService, |
|
| 155 | + SecuredDomainType.IP_BLOCKLIST_FOR_BEARER_TOKEN_ABUSE, |
|
| 156 | + stringMessages.unableToLoadIpsBlockedForBearerTokenAbuse(), stringMessages, errorReporter) { |
|
| 157 | + @Override |
|
| 158 | + protected void fetchData(AsyncCallback<HashMap<String, TimedLock>> callback) { |
|
| 159 | + sailingServiceWrite.getClientIPBasedTimedLocksForBearerTokenAbuse(callback); |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + @Override |
|
| 163 | + protected void unlockIP(String ip, AsyncCallback<Void> asyncCallback) { |
|
| 164 | + sailingService.releaseBearerTokenLockOnIp(ip, asyncCallback); |
|
| 165 | + } |
|
| 166 | + }; |
|
| 167 | + panel.setContentWidget(table.asWidget()); |
|
| 168 | + return panel; |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + private Widget createUserCreationAbusePanel() { |
|
| 172 | + final ServerDataCaptionPanel panel = new ServerDataCaptionPanel(stringMessages.ipsLockedForUserCreationAbuse(), 3); |
|
| 173 | + panel.ensureDebugId("userCreationAbusePanel"); |
|
| 174 | + final IPBlocklistTableWrapper table = new IPBlocklistTableWrapper(sailingService, userService, |
|
| 175 | + SecuredDomainType.IP_BLOCKLIST_FOR_USER_CREATION_ABUSE, |
|
| 176 | + stringMessages.unableToLoadIpsBlockedForUserCreationAbuse(), stringMessages, errorReporter) { |
|
| 177 | + @Override |
|
| 178 | + protected void fetchData(AsyncCallback<HashMap<String, TimedLock>> callback) { |
|
| 179 | + sailingServiceWrite.getClientIPBasedTimedLocksForUserCreation(callback); |
|
| 180 | + } |
|
| 181 | + |
|
| 182 | + @Override |
|
| 183 | + protected void unlockIP(String ip, AsyncCallback<Void> asyncCallback) { |
|
| 184 | + sailingService.releaseUserCreationLockOnIp(ip, asyncCallback); |
|
| 185 | + } |
|
| 186 | + }; |
|
| 187 | + panel.setContentWidget(table.asWidget()); |
|
| 188 | + return panel; |
|
| 150 | 189 | } |
| 151 | 190 | |
| 152 | 191 | private Widget createCORSFilterConfigurationUI() { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
| ... | ... | @@ -2562,4 +2562,10 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, |
| 2562 | 2562 | String successfullyCopiedPairings(); |
| 2563 | 2563 | String selectFromRaceColumn(); |
| 2564 | 2564 | String selectToRaceColumn(); |
| 2565 | + String unableToLoadIpsBlockedForBearerTokenAbuse(); |
|
| 2566 | + String ipAddress(); |
|
| 2567 | + String filterIpAddresses(); |
|
| 2568 | + String unlock(); |
|
| 2569 | + String ipsLockedForUserCreationAbuse(); |
|
| 2570 | + String unableToLoadIpsBlockedForUserCreationAbuse(); |
|
| 2565 | 2571 | } |
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
| ... | ... | @@ -2599,4 +2599,10 @@ selectRaceColumnsWhosePairingsToCopy=Race columns whose pairings to copy |
| 2599 | 2599 | errorCopyingPairings=Error copying pairings: {0} |
| 2600 | 2600 | successfullyCopiedPairings=Successfully copied pairings |
| 2601 | 2601 | selectFromRaceColumn=Select race column from where to start copying pairings |
| 2602 | -selectToRaceColumn=Select race colunm to where to copy pairings |
|
| ... | ... | \ No newline at end of file |
| 0 | +selectToRaceColumn=Select race colunm to where to copy pairings |
|
| 1 | +unableToLoadIpsBlockedForBearerTokenAbuse=Unable to load IPs blocked for bearer token abuse |
|
| 2 | +ipAddress=IP Address |
|
| 3 | +filterIpAddresses=Filter IP Addresses |
|
| 4 | +unlock=Unlock |
|
| 5 | +ipsLockedForUserCreationAbuse=IPs Locked for User Creation Abuse |
|
| 6 | +unableToLoadIpsBlockedForUserCreationAbuse=Unable to load IPs Blocked for User Creation Abuse |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
| ... | ... | @@ -2593,4 +2593,10 @@ errorCopyingPairings=Fehler beim Kopieren der Zuordnungen: {0} |
| 2593 | 2593 | successfullyCopiedPairings=Zuordnungen erfolgreich kopiert |
| 2594 | 2594 | selectFromRaceColumn=Spalte auswählen, ab der die Zuordnungen kopiert werden sollen |
| 2595 | 2595 | selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden sollen |
| 2596 | -ipsLockedForBearerTokenAbuse=IPs wegen Missbrauchs von Bearer-Token gesperrt |
|
| ... | ... | \ No newline at end of file |
| 0 | +unableToLoadIpsBlockedForBearerTokenAbuse=Aufgrund von Missbrauch des Inhabertokens blockierte IPs können nicht geladen werden |
|
| 1 | +ipsLockedForBearerTokenAbuse=IPs wegen Missbrauchs von Bearer-Token gesperrt |
|
| 2 | +ipAddress=IP-Addresse |
|
| 3 | +filterIpAddresses=IP-Adressen filtern |
|
| 4 | +unlock=Entsperren |
|
| 5 | +ipsLockedForUserCreationAbuse=Wegen Missbrauchs bei der Benutzererstellung gesperrte IPs |
|
| 6 | +unableToLoadIpsBlockedForUserCreationAbuse=Wegen Missbrauchs der Benutzererstellung blockierte IPs können nicht geladen werden |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/core/ApiContext.java
| ... | ... | @@ -30,6 +30,7 @@ public class ApiContext { |
| 30 | 30 | public static final String SECURITY_CONTEXT = "security"; //$NON-NLS-1$ |
| 31 | 31 | public static final String ADMIN_USERNAME = "admin"; //$NON-NLS-1$ |
| 32 | 32 | public static final String ADMIN_PASSWORD = "admin"; //$NON-NLS-1$ |
| 33 | + public static final String INVALID_TOKEN_SAMPLE = "INVALID_TOKEN_SAMPLE"; |
|
| 33 | 34 | |
| 34 | 35 | private static final Logger logger = Logger.getLogger(ApiContext.class.getName()); |
| 35 | 36 | |
| ... | ... | @@ -75,6 +76,19 @@ public class ApiContext { |
| 75 | 76 | } |
| 76 | 77 | |
| 77 | 78 | /** |
| 79 | + * Creates an ApiContext with an invalid token. Useful for testing. |
|
| 80 | + * |
|
| 81 | + * @param contextRoot |
|
| 82 | + * server instance |
|
| 83 | + * @param context |
|
| 84 | + * web application context |
|
| 85 | + * @return ApiContext with invalid token |
|
| 86 | + */ |
|
| 87 | + public static ApiContext createApiContextWithInvalidToken(String contextRoot, String context) { |
|
| 88 | + return new ApiContext(contextRoot, context, INVALID_TOKEN_SAMPLE); |
|
| 89 | + } |
|
| 90 | + |
|
| 91 | + /** |
|
| 78 | 92 | * Creates an ApiContext with administrator privileges. |
| 79 | 93 | * |
| 80 | 94 | * @param contextRoot |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/IpBlocklistPanelPO.java
| ... | ... | @@ -0,0 +1,78 @@ |
| 1 | +package com.sap.sailing.selenium.pages.adminconsole.advanced; |
|
| 2 | + |
|
| 3 | +import org.openqa.selenium.WebDriver; |
|
| 4 | +import org.openqa.selenium.WebElement; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.selenium.core.BySeleniumId; |
|
| 7 | +import com.sap.sailing.selenium.core.FindBy; |
|
| 8 | +import com.sap.sailing.selenium.pages.PageArea; |
|
| 9 | +import com.sap.sailing.selenium.pages.gwt.CellTablePO; |
|
| 10 | +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; |
|
| 11 | + |
|
| 12 | +public class IpBlocklistPanelPO extends PageArea { |
|
| 13 | + static class TablePO extends CellTablePO<IPLockEntry> { |
|
| 14 | + public TablePO(WebDriver driver, WebElement element) { |
|
| 15 | + super(driver, element); |
|
| 16 | + } |
|
| 17 | + |
|
| 18 | + @Override |
|
| 19 | + protected IPLockEntry createDataEntry(WebElement element) { |
|
| 20 | + return new IPLockEntry(this, element); |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | + } |
|
| 24 | + |
|
| 25 | + public static class IPLockEntry extends DataEntryPO { |
|
| 26 | + private static final String IP_COLUMN = "IP Address"; |
|
| 27 | + private static final String LOCKED_UNTIL_COLUMN = "Locked until"; |
|
| 28 | + |
|
| 29 | + protected IPLockEntry(CellTablePO<IPLockEntry> table, WebElement element) { |
|
| 30 | + super(table, element); |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + @Override |
|
| 34 | + public String getIdentifier() { |
|
| 35 | + return getIp(); |
|
| 36 | + } |
|
| 37 | + |
|
| 38 | + public String getIp() { |
|
| 39 | + return getColumnContent(IP_COLUMN); |
|
| 40 | + } |
|
| 41 | + |
|
| 42 | + public String getLockedUntil() { |
|
| 43 | + return getColumnContent(LOCKED_UNTIL_COLUMN); |
|
| 44 | + } |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | + public IpBlocklistPanelPO(WebDriver driver, WebElement element) { |
|
| 48 | + super(driver, element); |
|
| 49 | + final WebElement cellTableWebElement = driver.findElement(new BySeleniumId("cellTable")); |
|
| 50 | + this.cellTable = new TablePO(driver, cellTableWebElement); |
|
| 51 | + } |
|
| 52 | + |
|
| 53 | + @FindBy(how = BySeleniumId.class, using = "refreshButton") |
|
| 54 | + private WebElement refreshButton; |
|
| 55 | + |
|
| 56 | + @FindBy(how = BySeleniumId.class, using = "unlockButton") |
|
| 57 | + private WebElement unlockButton; |
|
| 58 | + |
|
| 59 | + private final TablePO cellTable; |
|
| 60 | + |
|
| 61 | + public void refresh() { |
|
| 62 | + refreshButton.click(); |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + /** |
|
| 66 | + * @return true if IP was found, false if not found |
|
| 67 | + */ |
|
| 68 | + public boolean expectIpInTable(final String ip) { |
|
| 69 | + final IPLockEntry entry = cellTable.getEntry(ip); |
|
| 70 | + final boolean wasFound = entry != null; |
|
| 71 | + return wasFound; |
|
| 72 | + } |
|
| 73 | + |
|
| 74 | + public void unblockIP(String ip) { |
|
| 75 | + cellTable.getEntry(ip).select(); |
|
| 76 | + unlockButton.click(); |
|
| 77 | + } |
|
| 78 | +} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/advanced/LocalServerPO.java
| ... | ... | @@ -15,12 +15,28 @@ public class LocalServerPO extends PageArea { |
| 15 | 15 | |
| 16 | 16 | @FindBy(how = BySeleniumId.class, using = "isSelfServiceServerCheckbox-input") |
| 17 | 17 | private WebElement isSelfServiceServerCheckbox; |
| 18 | - |
|
| 18 | + |
|
| 19 | 19 | @FindBy(how = BySeleniumId.class, using = "isPublicServerCheckbox-input") |
| 20 | 20 | private WebElement isPublicServerCheckbox; |
| 21 | - |
|
| 21 | + |
|
| 22 | 22 | @FindBy(how = BySeleniumId.class, using = "isStandaloneServerCheckbox-input") |
| 23 | 23 | private WebElement isStandaloneServerCheckbox; |
| 24 | + |
|
| 25 | + @FindBy(how = BySeleniumId.class, using = "bearerTokenAbusePanel") |
|
| 26 | + private WebElement bearerTokenAbusePanel; |
|
| 27 | + |
|
| 28 | + @FindBy(how = BySeleniumId.class, using = "userCreationAbusePanel") |
|
| 29 | + private WebElement userCreationAbusePanel; |
|
| 30 | + |
|
| 31 | + public IpBlocklistPanelPO getBearerTokenAbusePO() { |
|
| 32 | + final WebElement wrappedTable = bearerTokenAbusePanel.findElement(new BySeleniumId("wrappedTable")); |
|
| 33 | + return new IpBlocklistPanelPO(this.driver, wrappedTable); |
|
| 34 | + } |
|
| 35 | + |
|
| 36 | + public IpBlocklistPanelPO getUserCreationAbusePO() { |
|
| 37 | + final WebElement wrappedTable = userCreationAbusePanel.findElement(new BySeleniumId("wrappedTable")); |
|
| 38 | + return new IpBlocklistPanelPO(this.driver, wrappedTable); |
|
| 39 | + } |
|
| 24 | 40 | |
| 25 | 41 | public void setSelfServiceServer(boolean selfService) { |
| 26 | 42 | if (selfService != isSelfServiceServerCheckbox.isSelected()) { |
| ... | ... | @@ -28,14 +44,14 @@ public class LocalServerPO extends PageArea { |
| 28 | 44 | awaitServerConfigurationUpdated(); |
| 29 | 45 | } |
| 30 | 46 | } |
| 31 | - |
|
| 47 | + |
|
| 32 | 48 | public void setPublicServer(boolean publicServer) { |
| 33 | 49 | if (publicServer != isPublicServerCheckbox.isSelected()) { |
| 34 | 50 | isPublicServerCheckbox.click(); |
| 35 | 51 | awaitServerConfigurationUpdated(); |
| 36 | 52 | } |
| 37 | 53 | } |
| 38 | - |
|
| 54 | + |
|
| 39 | 55 | public void setStandaloneServer(boolean standalone) { |
| 40 | 56 | if (standalone != isStandaloneServerCheckbox.isSelected()) { |
| 41 | 57 | isStandaloneServerCheckbox.click(); |
| ... | ... | @@ -52,5 +68,4 @@ public class LocalServerPO extends PageArea { |
| 52 | 68 | final String updating = isSelfServiceServerCheckbox.getAttribute("updating"); |
| 53 | 69 | return "true".equals(updating); |
| 54 | 70 | } |
| 55 | - |
|
| 56 | 71 | } |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestIpAbuse.java
| ... | ... | @@ -1,32 +0,0 @@ |
| 1 | -package com.sap.sailing.selenium.test.adminconsole; |
|
| 2 | - |
|
| 3 | -import static org.junit.jupiter.api.Assertions.assertTrue; |
|
| 4 | - |
|
| 5 | -import org.junit.jupiter.api.BeforeEach; |
|
| 6 | -import org.junit.jupiter.api.Test; |
|
| 7 | - |
|
| 8 | -import com.sap.sailing.selenium.core.SeleniumTestCase; |
|
| 9 | -import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; |
|
| 10 | -import com.sap.sailing.selenium.pages.adminconsole.advanced.LocalServerPO; |
|
| 11 | -import com.sap.sailing.selenium.test.AbstractSeleniumTest; |
|
| 12 | - |
|
| 13 | -public class TestIpAbuse extends AbstractSeleniumTest { |
|
| 14 | - @Override |
|
| 15 | - @BeforeEach |
|
| 16 | - public void setUp() { |
|
| 17 | - clearState(getContextRoot()); |
|
| 18 | - super.setUp(); |
|
| 19 | - } |
|
| 20 | - |
|
| 21 | - @SeleniumTestCase |
|
| 22 | - public void testUnlockIpBannedForBearerTokenAbuse() { |
|
| 23 | - final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 24 | - final LocalServerPO localServerPanel = adminConsole.goToLocalServerPanel(); |
|
| 25 | - assertTrue(true); |
|
| 26 | - } |
|
| 27 | - |
|
| 28 | - @Test |
|
| 29 | - public void testTest() { |
|
| 30 | - assertTrue(true); |
|
| 31 | - } |
|
| 32 | -} |
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestIpLocking.java
| ... | ... | @@ -0,0 +1,106 @@ |
| 1 | +package com.sap.sailing.selenium.test.adminconsole; |
|
| 2 | + |
|
| 3 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
|
| 4 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
|
| 5 | + |
|
| 6 | +import java.util.HashMap; |
|
| 7 | +import java.util.Map; |
|
| 8 | + |
|
| 9 | +import org.junit.jupiter.api.BeforeEach; |
|
| 10 | + |
|
| 11 | +import com.sap.sailing.selenium.api.core.ApiContext; |
|
| 12 | +import com.sap.sailing.selenium.api.core.HttpException.Unauthorized; |
|
| 13 | +import com.sap.sailing.selenium.api.event.PreferencesApi; |
|
| 14 | +import com.sap.sailing.selenium.api.event.SecurityApi; |
|
| 15 | +import com.sap.sailing.selenium.core.SeleniumTestCase; |
|
| 16 | +import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; |
|
| 17 | +import com.sap.sailing.selenium.pages.adminconsole.advanced.IpBlocklistPanelPO; |
|
| 18 | +import com.sap.sailing.selenium.test.AbstractSeleniumTest; |
|
| 19 | + |
|
| 20 | +public class TestIpLocking extends AbstractSeleniumTest { |
|
| 21 | + @Override |
|
| 22 | + @BeforeEach |
|
| 23 | + public void setUp() { |
|
| 24 | + clearState(getContextRoot()); |
|
| 25 | + super.setUp(); |
|
| 26 | + } |
|
| 27 | + |
|
| 28 | + @SeleniumTestCase |
|
| 29 | + public void testUnlockingForBearerTokenAbuser() throws InterruptedException { |
|
| 30 | + final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 31 | + final IpBlocklistPanelPO tablePO = adminConsole.goToLocalServerPanel().getBearerTokenAbusePO(); |
|
| 32 | + attemptBearerTokenAbuse(5); |
|
| 33 | + tablePO.refresh(); |
|
| 34 | + final String ip = "127.0.0.1"; |
|
| 35 | + assertTrue(tablePO.expectIpInTable(ip)); |
|
| 36 | + tablePO.unblockIP(ip); |
|
| 37 | + assertFalse(tablePO.expectIpInTable(ip)); |
|
| 38 | + attemptValidBearerTokenUse(); |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + private void attemptValidBearerTokenUse() { |
|
| 42 | + // prepare api |
|
| 43 | + final ApiContext ctx = ApiContext.createAdminApiContext(getContextRoot(), ApiContext.SECURITY_CONTEXT); |
|
| 44 | + final Map<String, String> prefObjectAttr = new HashMap<String, String>(); |
|
| 45 | + prefObjectAttr.put("key1", "value1"); |
|
| 46 | + PreferencesApi preferencesApi = new PreferencesApi(); |
|
| 47 | + preferencesApi.createPreference(ctx, "pref1", prefObjectAttr); |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + private void attemptBearerTokenAbuse(final int attempts) throws InterruptedException { |
|
| 51 | + // prepare api |
|
| 52 | + final ApiContext wrongCtx = ApiContext.createApiContextWithInvalidToken(getContextRoot(), |
|
| 53 | + ApiContext.SECURITY_CONTEXT); |
|
| 54 | + final Map<String, String> prefObjectAttr = new HashMap<String, String>(); |
|
| 55 | + prefObjectAttr.put("key1", "value1"); |
|
| 56 | + final PreferencesApi preferencesApi = new PreferencesApi(); |
|
| 57 | + for (int i = 0; i < attempts; i++) { |
|
| 58 | + // call api |
|
| 59 | + try { |
|
| 60 | + preferencesApi.createPreference(wrongCtx, "pref1", prefObjectAttr); |
|
| 61 | + } catch (Unauthorized e) { |
|
| 62 | + // do nothing as this is expected |
|
| 63 | + } |
|
| 64 | + // wait for lock to expire |
|
| 65 | + long lockDuration = (long) Math.pow(2, i) * 1000; |
|
| 66 | + boolean isFinalAttempt = i == (attempts - 1); |
|
| 67 | + if (!isFinalAttempt) { |
|
| 68 | + Thread.sleep(lockDuration); |
|
| 69 | + } |
|
| 70 | + } |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + @SeleniumTestCase |
|
| 74 | + public void testUnlockingForUserCreationAbuser() throws InterruptedException { |
|
| 75 | + final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); |
|
| 76 | + final IpBlocklistPanelPO tablePO = adminConsole.goToLocalServerPanel().getUserCreationAbusePO(); |
|
| 77 | + attemptUserCreationAbuse(5); |
|
| 78 | + tablePO.refresh(); |
|
| 79 | + final String ip = "127.0.0.1"; |
|
| 80 | + assertTrue(tablePO.expectIpInTable(ip)); |
|
| 81 | + tablePO.unblockIP(ip); |
|
| 82 | + assertFalse(tablePO.expectIpInTable(ip)); |
|
| 83 | + attemptValidBearerTokenUse(); |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + private void attemptUserCreationAbuse(final int attempts) throws InterruptedException { |
|
| 87 | + for (int i = 0; i < attempts; i++) { |
|
| 88 | + attemptUserCreation(String.valueOf(i)); |
|
| 89 | + // wait for lock to expire |
|
| 90 | + long lockDuration = (long) Math.pow(2, i) * 1000; |
|
| 91 | + boolean isFinalAttempt = i == (attempts - 1); |
|
| 92 | + if (!isFinalAttempt) { |
|
| 93 | + Thread.sleep(lockDuration); |
|
| 94 | + } |
|
| 95 | + } |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + private boolean attemptUserCreation(String seed) { |
|
| 99 | + try { |
|
| 100 | + SecurityApi.createUser("USERNAME" + seed, "PASSWORD").run(); |
|
| 101 | + return true; |
|
| 102 | + } catch (Exception e) { |
|
| 103 | + return false; |
|
| 104 | + } |
|
| 105 | + } |
|
| 106 | +} |
java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java
| ... | ... | @@ -8,11 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertNull; |
| 8 | 8 | import static org.junit.jupiter.api.Assertions.assertSame; |
| 9 | 9 | import static org.junit.jupiter.api.Assertions.assertTrue; |
| 10 | 10 | |
| 11 | +import java.io.IOException; |
|
| 11 | 12 | import java.net.UnknownHostException; |
| 12 | 13 | import java.util.Arrays; |
| 13 | 14 | import java.util.Collections; |
| 14 | 15 | import java.util.HashMap; |
| 15 | 16 | import java.util.HashSet; |
| 17 | +import java.util.Locale; |
|
| 16 | 18 | import java.util.Map; |
| 17 | 19 | import java.util.Set; |
| 18 | 20 | import java.util.UUID; |
| ... | ... | @@ -276,6 +278,46 @@ public class LoginTest { |
| 276 | 278 | assertTrue(PermissionChecker.isPermitted(new WildcardPermission("a:b:c"), user, allUser, null, null)); |
| 277 | 279 | } |
| 278 | 280 | |
| 281 | + @Test |
|
| 282 | + public void testUnlockBearerTokenAbuser() throws UserStoreManagementException, IOException, InterruptedException { |
|
| 283 | + final String localhost = "127.0.0.1"; |
|
| 284 | + final int attempts = 4; |
|
| 285 | + for(int i = 0; i < attempts; i++) { |
|
| 286 | + securityService.failedBearerTokenAuthentication(localhost); |
|
| 287 | + final long lockDuration = (long) Math.pow(2, i) * 1000; |
|
| 288 | + final boolean isFinalAttempt = i == (attempts - 1); |
|
| 289 | + if (!isFinalAttempt) { |
|
| 290 | + Thread.sleep(lockDuration); |
|
| 291 | + } |
|
| 292 | + } |
|
| 293 | + assertTrue(securityService.isClientIPLockedForBearerTokenAuthentication(localhost)); |
|
| 294 | + securityService.releaseBearerTokenLockOnIp(localhost); |
|
| 295 | + assertFalse(securityService.isClientIPLockedForBearerTokenAuthentication(localhost)); |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + @Test |
|
| 299 | + public void testUnlockUserCreationAbuser() throws UserStoreManagementException, IOException, InterruptedException, MailException { |
|
| 300 | + final String localhost = "127.0.0.1"; |
|
| 301 | + final int attempts = 4; |
|
| 302 | + for(int i = 0; i < attempts; i++) { |
|
| 303 | + try { |
|
| 304 | + securityService.createSimpleUser("USERNAME" + i, "a@b.c", "PASSWORD", "The User", "SAP SE", |
|
| 305 | + /* validation URL */ Locale.ENGLISH, null, null, /* clientIP */ localhost, |
|
| 306 | + /* enforce strong password */ false); |
|
| 307 | + } catch (UserManagementException e) { |
|
| 308 | + // do nothing, expected due to lock |
|
| 309 | + } |
|
| 310 | + final long lockDuration = (long) Math.pow(2, i) * 1000; |
|
| 311 | + final boolean isFinalAttempt = i == (attempts - 1); |
|
| 312 | + if (!isFinalAttempt) { |
|
| 313 | + Thread.sleep(lockDuration); |
|
| 314 | + } |
|
| 315 | + } |
|
| 316 | + assertTrue(securityService.isUserCreationLockedForClientIP(localhost)); |
|
| 317 | + securityService.releaseUserCreationLockOnIp(localhost); |
|
| 318 | + assertFalse(securityService.isUserCreationLockedForClientIP(localhost)); |
|
| 319 | + } |
|
| 320 | + |
|
| 279 | 321 | private UserStoreImpl createAndLoadUserStore() throws UserStoreManagementException { |
| 280 | 322 | final UserStoreImpl store = new UserStoreImpl(DEFAULT_TENANT_NAME); |
| 281 | 323 | store.loadAndMigrateUsers(); |
java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java
| ... | ... | @@ -918,12 +918,15 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica |
| 918 | 918 | * Used in conjunction with {@link #failedBearerTokenAuthentication(String)} and |
| 919 | 919 | * {@link #successfulBearerTokenAuthentication(String)}. If and only if a locking state for the combination |
| 920 | 920 | * of {@code clientIP} and {@code userAgent} is known and still locked, {@code true} is returned. Unlocking |
| 921 | - * will bappen by calling {@link #successfulBearerTokenAuthentication(String)} with an equal combination |
|
| 922 | - * of {@code clientIP} and {@code userAgent}. Invoking {@link #failedBearerTokenAuthentication(String)} |
|
| 923 | - * will establish (if not yet locked) or extend the locking duration for the combination. |
|
| 921 | + * will happen by calling {@link #successfulBearerTokenAuthentication(String)} with an equal combination of |
|
| 922 | + * {@code clientIP} and {@code userAgent} or by calling {@link releaseBearerTokenLockOnIp(String)}. Invoking |
|
| 923 | + * {@link #failedBearerTokenAuthentication(String)} will establish (if not yet locked) or extend the locking |
|
| 924 | + * duration for the combination. |
|
| 924 | 925 | */ |
| 925 | 926 | boolean isClientIPLockedForBearerTokenAuthentication(String clientIP); |
| 926 | 927 | |
| 928 | + boolean isUserCreationLockedForClientIP(String clientIP); |
|
| 929 | + |
|
| 927 | 930 | void fileTakedownNotice(TakedownNoticeRequestContext takedownNoticeRequestContext) throws MailException; |
| 928 | 931 | |
| 929 | 932 | /** |
| ... | ... | @@ -941,8 +944,8 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica |
| 941 | 944 | */ |
| 942 | 945 | Iterable<User> getUsersToInformAboutReplicaSet(String serverName, |
| 943 | 946 | Optional<com.sap.sse.security.shared.HasPermissions.Action> alsoSendToAllUsersWithThisPermissionOnReplicaSet); |
| 944 | - |
|
| 947 | + |
|
| 945 | 948 | HashMap<String, TimedLock> getClientIPBasedTimedLocksForUserCreation(); |
| 946 | - |
|
| 949 | + |
|
| 947 | 950 | HashMap<String, TimedLock> getClientIPBasedTimedLocksForBearerTokenAbuse(); |
| 948 | 951 | } |
java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
| ... | ... | @@ -1006,7 +1006,7 @@ implements ReplicableSecurityService, ClearStateTestSupport { |
| 1006 | 1006 | |
| 1007 | 1007 | @Override |
| 1008 | 1008 | public void releaseBearerTokenLockOnIp(String ip) { |
| 1009 | - logger.info("Releasing timed lock for user creation at IP "+ip); |
|
| 1009 | + logger.info("Releasing timed lock for bearer token abuse at IP "+ip); |
|
| 1010 | 1010 | apply(new ReleaseBearerTokenLockOnIpOperation(ip)); |
| 1011 | 1011 | } |
| 1012 | 1012 | |
| ... | ... | @@ -1192,6 +1192,12 @@ implements ReplicableSecurityService, ClearStateTestSupport { |
| 1192 | 1192 | } |
| 1193 | 1193 | |
| 1194 | 1194 | @Override |
| 1195 | + public boolean isUserCreationLockedForClientIP(String clientIP) { |
|
| 1196 | + final TimedLock timedLock = clientIPBasedTimedLocksForUserCreation.get(escapeNullClientIP(clientIP)); |
|
| 1197 | + return timedLock != null && timedLock.isLocked(); |
|
| 1198 | + } |
|
| 1199 | + |
|
| 1200 | + @Override |
|
| 1195 | 1201 | public TimedLock internalRecordUserCreationFromClientIP(String clientIP) { |
| 1196 | 1202 | final TimedLock result = new TimedLockImpl(TimePoint.now().plus(DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION), |
| 1197 | 1203 | DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION); |