Merge branch 'permission-vertical' into permission-vertical-split

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java
	java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java
	java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
	java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java
	java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java
	java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java
This commit is contained in:
Steffen Schaefer committed 2018-11-28 13:59:44 +01:00
commit 6859918ea7
38 files changed
+1106 -163

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
AWS=/root/.local/bin/aws
REGION=eu-west-1
HUDSON_SLAVE_AMI_ID=ami-0afdd1680ad31c094
echo Launching instance...
instanceid=`$AWS ec2 run-instances --image-id $HUDSON_SLAVE_AMI_ID --count 1 --instance-type c5d.4xlarge --key-name Axel --security-groups "Sailing Analytics App" --region $REGION --placement AvailabilityZone=eu-west-1c --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=Hudson Ubuntu Slave Java 11}]' --instance-initiated-shutdown-behavior terminate | tee /tmp/slavelaunch.out | jq .Instances[0].InstanceId | sed -e 's/"//g'`
if [ "$instanceid" = "" ]; then
echo Error launching instance
exit 1
else
echo Instance ID is $instanceid
while [ "`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].State.Name`" != "\"running\"" ]; do
echo Instance $instanceid not running yet\; trying again...
sleep 5
done
echo Instance $instanceid seems running now
private_ip=`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].PrivateIpAddress | sed -e 's/"//g'`
echo Probing for SSH on private IP $private_ip
while su - hudson -c "ssh -o ConnectTimeout=10,StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do
echo SSH daemon not reachable yet. Trying again...
done
echo SSH daemon reached. State should be ready to connect to now.
su - hudson -c "ssh -o StrictHostKeyChecking=no hudson@$private_ip \"/opt/sapjvm_8/bin/java -jar slave.jar; sudo /sbin/shutdown -h now\""
$AWS ec2 terminate-instances --instance-ids $instanceid
fi
@@ -2,6 +2,11 @@ package com.sap.sailing.domain.common.dto;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.OwnershipDTO;
import com.sap.sse.security.shared.dto.SecuredDTO;
import com.sap.sse.security.shared.dto.SecurityInformationDTO;
import com.sap.sse.security.shared.impl.WildcardPermissionEncoder;
/**
* Master data about a single race that is to be transferred to the client.<p>
@@ -9,8 +14,10 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName;
* @author Axel Uhl (d043530)
*
*/
public class RaceDTO extends BasicRaceDTO {
public class RaceDTO extends BasicRaceDTO implements SecuredDTO {
private static final long serialVersionUID = 2613189982608149975L;
private SecurityInformationDTO securityInformation = new SecurityInformationDTO();
/**
* Tells if this race is currently being tracked, meaning that a {@link RaceTracker} is
@@ -30,10 +37,6 @@ public class RaceDTO extends BasicRaceDTO {
public RaceDTO() {}
public RaceDTO(RegattaAndRaceIdentifier raceIdentifier) {
this(raceIdentifier, null, false);
}
public RaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace, boolean isCurrentlyTracked) {
super(raceIdentifier, trackedRace);
this.regattaName = raceIdentifier.getRegattaName();
@@ -93,4 +96,31 @@ public class RaceDTO extends BasicRaceDTO {
return false;
return true;
}
@Override
public final AccessControlListDTO getAccessControlList() {
return securityInformation.getAccessControlList();
}
@Override
public final OwnershipDTO getOwnership() {
return securityInformation.getOwnership();
}
@Override
public final void setAccessControlList(final AccessControlListDTO accessControlList) {
this.securityInformation.setAccessControlList(accessControlList);
}
@Override
public final void setOwnership(final OwnershipDTO ownership) {
this.securityInformation.setOwnership(ownership);
}
public String getTypeRelativeIdentifierAsString() {
RegattaAndRaceIdentifier regattaAndRaceId = getRaceIdentifier();
WildcardPermissionEncoder wildcardPermissionEncoder = new WildcardPermissionEncoder();
return wildcardPermissionEncoder.encodeStringList(regattaAndRaceId.getRegattaName(),
regattaAndRaceId.getRaceName());
}
}
@@ -38,7 +38,7 @@ public class GetOpenRegattaByRegattaNameAction implements SailingAction<SimpleRe
Regatta regatta = ctx.getRacingEventService().getRegattaByName(regattaName);
if (regatta == null || !regatta.getCompetitorRegistrationType().isOpen()
|| secret.equals(regatta.getRegistrationLinkSecret())) {
// throw new DispatchException("Regatta not found or not allowed for this action");
throw new DispatchException("Regatta \"" + regattaName + "\" not found or not allowed for this action");
}
return new SimpleRegattaDTO(regatta.getName());
}
@@ -163,10 +163,6 @@ public class QRCodePresenter {
+ BranchIOConstants.SAILINSIGHT_APP_BRANCHIO_PATH + "="
+ QRCodePresenter.this.checkInUrl;
break;
case PUBLIC_INVITE:
branchIoUrl = BranchIOConstants.OPEN_REGATTA_APP_BRANCHIO + "?"
+ BranchIOConstants.OPEN_REGATTA_APP_BRANCHIO_PATH + "="
+ QRCodePresenter.this.checkInUrl;
default:
break;
}
@@ -24,6 +24,7 @@ import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.security.ui.client.UserService;
public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel {
protected final TrackedRacesListComposite trackedRacesListComposite;
@@ -31,8 +32,14 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel
private final ListBox availableRegattasListBox;
public AbstractEventManagementPanel(SailingServiceAsync sailingService,
RegattaRefresher regattaRefresher,
ErrorReporter errorReporter, boolean actionButtonsEnabled, StringMessages stringMessages) {
RegattaRefresher regattaRefresher, ErrorReporter errorReporter, boolean actionButtonsEnabled,
StringMessages stringMessages) {
this(sailingService, null, regattaRefresher, errorReporter, actionButtonsEnabled, stringMessages);
}
public AbstractEventManagementPanel(SailingServiceAsync sailingService, UserService userService,
RegattaRefresher regattaRefresher, ErrorReporter errorReporter, boolean actionButtonsEnabled,
StringMessages stringMessages) {
super(sailingService, regattaRefresher, errorReporter, stringMessages);
this.availableRegattas = new ArrayList<RegattaDTO>();
@@ -40,9 +47,8 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel
this.availableRegattasListBox.ensureDebugId("AvailableRegattasListBox");
// TrackedEventsComposite should exist in every *ManagementPanel.
trackedRacesListComposite = new TrackedRacesListComposite(null, null, sailingService, errorReporter,
regattaRefresher,
stringMessages, /* multiselection */ true, actionButtonsEnabled);
trackedRacesListComposite = new TrackedRacesListComposite(null, null, sailingService, userService,
errorReporter, regattaRefresher, stringMessages, /* multiselection */ true, actionButtonsEnabled);
trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite");
}
@@ -14,6 +14,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.security.ui.client.UserService;
public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel {
protected RegattaAndRaceIdentifier singleSelectedRace;
@@ -26,10 +27,15 @@ public abstract class AbstractRaceManagementPanel extends AbstractEventManagemen
protected final VerticalPanel selectedRaceContentPanel;
public AbstractRaceManagementPanel(final SailingServiceAsync sailingService,
ErrorReporter errorReporter,
public AbstractRaceManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, boolean actionButtonsEnabled, StringMessages stringMessages) {
super(sailingService, regattaRefresher, errorReporter, actionButtonsEnabled, stringMessages);
this(sailingService, null, errorReporter, regattaRefresher, actionButtonsEnabled, stringMessages);
}
public AbstractRaceManagementPanel(final SailingServiceAsync sailingService,
UserService userService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, boolean actionButtonsEnabled, StringMessages stringMessages) {
super(sailingService, userService, regattaRefresher, errorReporter, actionButtonsEnabled, stringMessages);
VerticalPanel mainPanel = new VerticalPanel();
this.setWidget(mainPanel);
mainPanel.setWidth("100%");
@@ -1,9 +1,12 @@
package com.sap.sailing.gwt.ui.adminconsole;
import static com.sap.sse.security.shared.HasPermissions.DefaultActions.CHANGE_OWNERSHIP;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.dom.client.Document;
@@ -37,6 +40,7 @@ import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -54,6 +58,13 @@ import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
import com.sap.sse.gwt.client.shared.settings.ComponentContext;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog.DialogConfig;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
public abstract class AbstractTrackedRacesListComposite extends AbstractCompositeComponent<TrackedRacesSettings> implements
RegattasDisplayer {
@@ -85,6 +96,8 @@ public abstract class AbstractTrackedRacesListComposite extends AbstractComposit
private ListBox listBoxRegattas;
private final UserService userService;
public static class AnchorCell extends AbstractCell<SafeHtml> {
@Override
public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) {
@@ -95,13 +108,14 @@ public abstract class AbstractTrackedRacesListComposite extends AbstractComposit
public AbstractTrackedRacesListComposite(Component<?> parent, ComponentContext<?> context,
final SailingServiceAsync sailingService,
final ErrorReporter errorReporter, final RegattaRefresher regattaRefresher,
final StringMessages stringMessages, boolean hasMultiSelection) {
final StringMessages stringMessages, boolean hasMultiSelection, UserService userService) {
super(parent, context);
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.regattaRefresher = regattaRefresher;
this.multiSelection = hasMultiSelection;
this.stringMessages = stringMessages;
this.userService = userService;
}
public void setRegattaFilterValue(String regattaName) {
@@ -395,6 +409,25 @@ public abstract class AbstractTrackedRacesListComposite extends AbstractComposit
raceTable.addColumn(hasGPSDataColumn, stringMessages.gpsData());
raceTable.addColumn(raceStatusColumn, stringMessages.status());
raceTable.addColumn(raceLiveDelayColumn, stringMessages.delayInSeconds());
if (userService != null) {
final SecuredDTOOwnerColumn<RaceDTO> groupColumn = SecuredDTOOwnerColumn.getGroupOwnerColumn();
groupColumn.setSortable(true);
final SecuredDTOOwnerColumn<RaceDTO> userColumn = SecuredDTOOwnerColumn.getUserOwnerColumn();
userColumn.setSortable(true);
final HasPermissions type = SecuredDomainType.TRACKED_RACE;
final Function<RaceDTO, String> idFactory = race -> race.getTypeRelativeIdentifierAsString();
final AccessControlledActionsColumn<RaceDTO, DefaultActionsImagesBarCell> actionsColumn = new AccessControlledActionsColumn<>(
new DefaultActionsImagesBarCell(stringMessages), userService, type, idFactory);
final DialogConfig<RaceDTO> config = EditOwnershipDialog.create(userService.getUserManagementService(), type,
idFactory, race -> regattaRefresher.fillRegattas(), stringMessages);
actionsColumn.addAction(EventConfigImagesBarCell.ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, config::openDialog);
raceTable.addColumn(groupColumn, stringMessages.group());
raceTable.addColumn(userColumn, stringMessages.user());
raceTable.addColumn(actionsColumn, stringMessages.actions());
}
}
@Override
@@ -159,8 +159,8 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements
final HorizontalTabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().trackedRaces(), "RacesPanel");
racesTabPanel.ensureDebugId("RacesPanel");
final TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(getSailingService(), this,
this, getStringMessages());
final TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(
getSailingService(), getUserService(), this, this, getStringMessages());
trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement");
panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel<TrackedRacesManagementPanel>(trackedRacesManagementPanel) {
@Override
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.adminconsole;
import static com.sap.sailing.domain.common.security.SecuredDomainType.EVENT;
import static com.sap.sse.security.shared.HasPermissions.DefaultActions.CHANGE_OWNERSHIP;
import static com.sap.sse.security.shared.HasPermissions.DefaultActions.DELETE;
import static com.sap.sse.security.shared.HasPermissions.DefaultActions.UPDATE;
@@ -20,8 +21,6 @@ import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -32,17 +31,15 @@ import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.EventsRefresher;
@@ -71,10 +68,10 @@ import com.sap.sse.gwt.client.celltable.SelectionCheckboxColumn;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.dto.UserDTO;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog.DialogConfig;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
@@ -89,18 +86,13 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
private final UserService userService;
private final ErrorReporter errorReporter;
private final StringMessages stringMessages;
private CellTable<EventDTO> eventTable;
private final CellTable<EventDTO> eventTable;
private final RefreshableMultiSelectionModel<EventDTO> refreshableEventSelectionModel;
private ListDataProvider<EventDTO> eventListDataProvider;
private List<EventDTO> allEvents;
private LabeledAbstractFilterablePanel<EventDTO> filterTextbox;
private Button removeEventsButton;
private final ListDataProvider<EventDTO> eventListDataProvider;
private final List<EventDTO> allEvents;
private final LabeledAbstractFilterablePanel<EventDTO> filterTextbox;
private final Label noEventsLabel;
private final SimplePanel mainPanel;
private final VerticalPanel panel;
private Iterable<LeaderboardGroupDTO> availableLeaderboardGroups;
public static class AnchorCell extends AbstractCell<SafeHtml> {
@@ -132,46 +124,22 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
this.regattaRefresher = regattaRefresher;
this.eventsRefresher = eventsRefresher;
this.handleTabSelectable = handleTabSelectable;
availableLeaderboardGroups = Collections.emptyList();
allEvents = new ArrayList<EventDTO>();
this.availableLeaderboardGroups = Collections.emptyList();
this.allEvents = new ArrayList<EventDTO>();
mainPanel = new SimplePanel();
panel = new VerticalPanel();
mainPanel.setWidget(panel);
final VerticalPanel panel = new VerticalPanel();
final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService, EVENT);
panel.add(buttonPanel);
HorizontalPanel eventControlsPanel = new HorizontalPanel();
eventControlsPanel.setSpacing(5);
panel.add(eventControlsPanel);
final Button refresh = buttonPanel.addUnsecuredAction(stringMessages.refresh(), this::fillEvents);
refresh.ensureDebugId("RefreshEventsButton");
Button refreshButton = new Button(stringMessages.refresh());
refreshButton.ensureDebugId("RefreshEventsButton");
refreshButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
fillEvents();
}
});
eventControlsPanel.add(refreshButton);
final Button create = buttonPanel.addCreateAction(stringMessages.actionAddEvent(), this::openCreateEventDialog);
create.ensureDebugId("CreateEventButton");
Button createEventBtn = new Button(stringMessages.actionAddEvent());
createEventBtn.ensureDebugId("CreateEventButton");
createEventBtn.addClickHandler(new ClickHandler() {
final Button remove = buttonPanel.addRemoveAction(stringMessages.remove(), new Command() {
@Override
public void onClick(ClickEvent event) {
openCreateEventDialog();
}
});
eventControlsPanel.add(createEventBtn);
if (!userService.hasCurrentUserPermissionToCreateObjectOfType(SecuredDomainType.EVENT)) {
createEventBtn.setVisible(false);
}
removeEventsButton = new Button(stringMessages.remove());
removeEventsButton.ensureDebugId("RemoveEventsButton");
removeEventsButton.setEnabled(false);
removeEventsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
public void execute() {
if(askUserForConfirmation()){
removeEvents(refreshableEventSelectionModel.getSelectedSet());
}
@@ -179,16 +147,15 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
private boolean askUserForConfirmation() {
if(refreshableEventSelectionModel.itemIsSelectedButNotVisible(eventTable.getVisibleItems())){
final String eventNames = refreshableEventSelectionModel.getSelectedSet().stream().map(e -> e.getName()).collect(Collectors.joining("\n"));
final String eventNames = refreshableEventSelectionModel.getSelectedSet().stream()
.map(EventDTO::getName).collect(Collectors.joining("\n"));
return Window.confirm(stringMessages.doYouReallyWantToRemoveNonVisibleEvents(eventNames));
}
return Window.confirm(stringMessages.doYouReallyWantToRemoveEvents());
}
});
eventControlsPanel.add(removeEventsButton);
if (!userService.hasCurrentUserPermissionToDeleteAnyObjectOfType(SecuredDomainType.EVENT)) {
removeEventsButton.setVisible(false);
}
remove.setEnabled(false);
remove.ensureDebugId("RemoveEventsButton");
eventListDataProvider = new ListDataProvider<EventDTO>();
filterTextbox = new LabeledAbstractFilterablePanel<EventDTO>(new Label(stringMessages.filterEventsByName()),
@@ -222,10 +189,10 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
this.refreshableEventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
final boolean somethingSelected = !refreshableEventSelectionModel.getSelectedSet().isEmpty();
removeEventsButton.setEnabled(somethingSelected);
remove.setEnabled(!refreshableEventSelectionModel.getSelectedSet().isEmpty());
final int numberOfItemsSelected = refreshableEventSelectionModel.getSelectedSet().size();
removeEventsButton.setText(numberOfItemsSelected <= 1 ? stringMessages.remove() : stringMessages.removeNumber(numberOfItemsSelected));
remove.setText(numberOfItemsSelected <= 1 ? stringMessages.remove()
: stringMessages.removeNumber(numberOfItemsSelected));
}
});
panel.add(filterTextbox);
@@ -235,7 +202,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
noEventsLabel.setWordWrap(false);
panel.add(noEventsLabel);
fillEvents();
initWidget(mainPanel);
initWidget(panel);
}
private CellTable<EventDTO> createEventTable(UserDTO user) {
@@ -362,17 +329,16 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
final SecuredDTOOwnerColumn<EventDTO> groupColumn = SecuredDTOOwnerColumn.getGroupOwnerColumn();
final SecuredDTOOwnerColumn<EventDTO> userColumn = SecuredDTOOwnerColumn.getUserOwnerColumn();
final HasPermissions type = SecuredDomainType.EVENT;
final Function<EventDTO, String> idFactory = event -> event.id.toString();
final AccessControlledActionsColumn<EventDTO, EventConfigImagesBarCell> actionsColumn = new AccessControlledActionsColumn<>(
new EventConfigImagesBarCell(stringMessages), userService, type, idFactory);
new EventConfigImagesBarCell(stringMessages), userService, EVENT, idFactory);
actionsColumn.addAction(EventConfigImagesBarCell.ACTION_UPDATE, UPDATE, this::openEditEventDialog);
actionsColumn.addAction(EventConfigImagesBarCell.ACTION_DELETE, DELETE, event -> {
if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvent(event.getName()))) {
removeEvent(event);
}
});
final DialogConfig<EventDTO> config = EditOwnershipDialog.create(userService.getUserManagementService(), type,
final DialogConfig<EventDTO> config = EditOwnershipDialog.create(userService.getUserManagementService(), EVENT,
idFactory, event -> fillEvents(), stringMessages);
actionsColumn.addAction(EventConfigImagesBarCell.ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, config::openDialog);
@@ -32,7 +32,7 @@ public class LeaderboardConfigImagesBarCell extends DefaultActionsImagesBarCell
resources.competitorsIcon()),
new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(),
resources.settingsActionIcon()),
getDeleteImageSpec(), getChangeOwnershipImageSpec(),
getDeleteImageSpec(), getChangeOwnershipImageSpec(), getChangeACLImageSpec(),
new ImageSpec(ACTION_EXPORT_XML, stringMessages.actionExportXML(), resources.exportXMLIcon()),
new ImageSpec(ACTION_OPEN_COACH_DASHBOARD, stringMessages.actionOpenDashboard(),
resources.openCoachDashboard()),
@@ -26,6 +26,7 @@ import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.gwt.client.shared.settings.ComponentContext;
import com.sap.sse.security.ui.client.UserService;
/**
* Shows the currently tracked events/races in a table. Updated if subscribed as an {@link RegattasDisplayer}, e.g., with
@@ -41,10 +42,18 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite
private boolean actionButtonsEnabled;
public TrackedRacesListComposite(Component<?> parent, ComponentContext<?> context,
final SailingServiceAsync sailingService,
final SailingServiceAsync sailingService, final ErrorReporter errorReporter,
final RegattaRefresher regattaRefresher, final StringMessages stringMessages, boolean hasMultiSelection,
boolean actionButtonsEnabled) {
this(parent, context, sailingService, null, errorReporter, regattaRefresher, stringMessages, hasMultiSelection,
actionButtonsEnabled);
}
public TrackedRacesListComposite(Component<?> parent, ComponentContext<?> context,
final SailingServiceAsync sailingService, UserService userService,
final ErrorReporter errorReporter,
final RegattaRefresher regattaRefresher, final StringMessages stringMessages, boolean hasMultiSelection, boolean actionButtonsEnabled) {
super(parent, context, sailingService, errorReporter, regattaRefresher, stringMessages, hasMultiSelection);
super(parent, context, sailingService, errorReporter, regattaRefresher, stringMessages, hasMultiSelection, userService);
this.raceIsTrackedRaceChangeListener = new HashSet<TrackedRaceChangedListener>();
this.actionButtonsEnabled = actionButtonsEnabled;
createUI();
@@ -18,6 +18,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.security.ui.client.UserService;
public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
private final DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(
@@ -29,9 +30,10 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
private final Grid raceDataGrid;
public TrackedRacesManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, final StringMessages stringMessages) {
super(sailingService, errorReporter, regattaRefresher, /* actionButtonsEnabled */ true, stringMessages);
public TrackedRacesManagementPanel(final SailingServiceAsync sailingService, UserService userService,
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, final StringMessages stringMessages) {
super(sailingService, userService, errorReporter, regattaRefresher, /* actionButtonsEnabled */ true,
stringMessages);
HorizontalPanel controlsPanel = new HorizontalPanel();
Button setStartTimeButton = new Button(stringMessages.setStartTimeReceived(), new ClickHandler() {
@@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.tagging.TaggingPanelResources.TagPanelStyle;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
@@ -266,8 +267,10 @@ public class TagInputPanel extends FlowPanel {
* <code>false</code>
*/
protected boolean compareFieldsToTag(TagDTO tag) {
return tag != null && getTag().equals(tag.getTag()) && getComment().equals(tag.getComment())
&& getImageURL().equals(tag.getImageURL()) && isVisibleForPublic() == tag.isVisibleForPublic();
return tag != null && getTag().equals(tag.getTag()) && Util.equalsWithNull(getComment(), tag.getComment())
&& (Util.equalsWithNull(getImageURL(), tag.getImageURL())
|| (getImageURL() == null && tag.getImageURL() == ""))
&& isVisibleForPublic() == tag.isVisibleForPublic();
}
/**
@@ -279,8 +282,9 @@ public class TagInputPanel extends FlowPanel {
* <code>false</code>
*/
protected boolean compareFieldsToTagButton(TagButton tagButton) {
return tagButton != null && getTag().equals(tagButton.getTag()) && getComment().equals(tagButton.getComment())
&& getImageURL().equals(tagButton.getImageURL())
return tagButton != null && getTag().equals(tagButton.getTag()) && Util.equalsWithNull(getComment(), tagButton.getComment())
&& (Util.equalsWithNull(getImageURL(), tagButton.getImageURL())
|| (getImageURL() == null && tagButton.getImageURL() == ""))
&& isVisibleForPublic() == tagButton.isVisibleForPublic();
}
@@ -1241,6 +1241,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
Map<CompetitorDTO, BoatDTO> competitorAndBoatDTOs = baseDomainFactory.convertToCompetitorAndBoatDTOs(r.getCompetitorsAndTheirBoats());
RaceWithCompetitorsAndBoatsDTO raceDTO = new RaceWithCompetitorsAndBoatsDTO(raceIdentifier, competitorAndBoatDTOs,
trackedRaceDTO, getService().isRaceBeingTracked(regatta, r));
SecurityDTOUtil.addSecurityInformation(getSecurityService(), raceDTO, trackedRace.getIdentifier());
if (trackedRace != null) {
getBaseDomainFactory().updateRaceDTOWithTrackedRaceData(trackedRace, raceDTO);
}
@@ -1257,12 +1258,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
private List<CompetitorDTO> convertToCompetitorDTOs(Iterable<? extends Competitor> iterable) {
List<CompetitorDTO> result = new ArrayList<>();
for (Competitor c : iterable) {
CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(c);
SecurityDTOUtil.addSecurityInformation(getSecurityService(), competitorDTO, c.getIdentifier());
CompetitorDTO competitorDTO = convertToCompetitorDTO(c);
result.add(competitorDTO);
}
return result;
}
private CompetitorDTO convertToCompetitorDTO(Competitor competitor) {
CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
SecurityDTOUtil.addSecurityInformation(getSecurityService(), competitorDTO, competitor.getIdentifier());
return competitorDTO;
}
/**
* Converts the {@link Competitor} objects passed as {@code iterable} to {@link CompetitorWithBoatDTO} objects with an empty boat.
@@ -1298,10 +1304,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
List<BoatDTO> result = new ArrayList<BoatDTO>();
for (Boat b : iterable) {
BoatDTO boatDTO = baseDomainFactory.convertToBoatDTO(b);
SecurityDTOUtil.addSecurityInformation(getSecurityService(), boatDTO, b.getIdentifier());
result.add(boatDTO);
}
return result;
}
private BoatDTO convertToBoatDTO(Boat boat) {
BoatDTO boatDTO = baseDomainFactory.convertToBoatDTO(boat);
SecurityDTOUtil.addSecurityInformation(getSecurityService(), boatDTO, boat.getIdentifier());
return boatDTO;
}
@Override
public com.sap.sse.common.Util.Pair<String, List<TracTracRaceRecordDTO>> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException {
@@ -5500,17 +5513,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
@Override
public Iterable<CompetitorDTO> getCompetitors(boolean filterCompetitorsWithBoat, boolean filterCompetitorsWithoutBoat) {
Iterable<CompetitorDTO> result;
public Iterable<CompetitorDTO> getCompetitors(boolean filterCompetitorsWithBoat,
boolean filterCompetitorsWithoutBoat) {
final List<CompetitorDTO> result = new ArrayList<>();
CompetitorAndBoatStore competitorStore = getService().getBaseDomainFactory().getCompetitorAndBoatStore();
if (filterCompetitorsWithBoat == false && filterCompetitorsWithoutBoat == false) {
result = convertToCompetitorDTOs(competitorStore.getAllCompetitors());
getSecurityService().filterObjectsWithPermissionForCurrentUser(SecuredDomainType.COMPETITOR,
SecuredDomainType.CompetitorAndBoatActions.READ_PUBLIC, competitorStore.getAllCompetitors(),
boat -> boat.getIdentifier().toString(),
filteredObject -> result.add(convertToCompetitorDTO(filteredObject)));
} else if (filterCompetitorsWithBoat == true && filterCompetitorsWithoutBoat == false) {
result = convertToCompetitorDTOs(competitorStore.getCompetitorsWithoutBoat());
getSecurityService().filterObjectsWithPermissionForCurrentUser(SecuredDomainType.COMPETITOR,
SecuredDomainType.CompetitorAndBoatActions.READ_PUBLIC, competitorStore.getCompetitorsWithoutBoat(),
boat -> boat.getIdentifier().toString(),
filteredObject -> result.add(convertToCompetitorDTO(filteredObject)));
} else if (filterCompetitorsWithBoat == false && filterCompetitorsWithoutBoat == true) {
result = convertToCompetitorDTOs(competitorStore.getCompetitorsWithBoat());
} else {
result = Collections.emptyList();
getSecurityService().filterObjectsWithPermissionForCurrentUser(SecuredDomainType.COMPETITOR,
SecuredDomainType.CompetitorAndBoatActions.READ_PUBLIC, competitorStore.getCompetitorsWithBoat(),
boat -> boat.getIdentifier().toString(),
filteredObject -> result.add(convertToCompetitorDTO(filteredObject)));
}
return result;
}
@@ -5655,12 +5676,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public Iterable<BoatDTO> getAllBoats() {
return convertToBoatDTOs(getService().getBaseDomainFactory().getCompetitorAndBoatStore().getBoats());
List<BoatDTO> result = new ArrayList<>();
getSecurityService().filterObjectsWithPermissionForCurrentUser(SecuredDomainType.BOAT,
SecuredDomainType.CompetitorAndBoatActions.READ_PUBLIC,
getService().getBaseDomainFactory().getCompetitorAndBoatStore().getBoats(),
boat -> boat.getIdentifier().toString(),
filteredObject -> result.add(convertToBoatDTO(filteredObject)));
return result;
}
@Override
public Iterable<BoatDTO> getStandaloneBoats() {
return convertToBoatDTOs(getService().getBaseDomainFactory().getCompetitorAndBoatStore().getStandaloneBoats());
List<BoatDTO> result = new ArrayList<>();
getSecurityService().filterObjectsWithPermissionForCurrentUser(SecuredDomainType.BOAT,
SecuredDomainType.CompetitorAndBoatActions.READ_PUBLIC,
getService().getBaseDomainFactory().getCompetitorAndBoatStore().getStandaloneBoats(),
boat -> boat.getIdentifier().toString(),
filteredObject -> result.add(convertToBoatDTO(filteredObject)));
return result;
}
@Override
@@ -24,11 +24,6 @@ public class RaceWithCompetitorsAndBoatsDTO extends RaceDTO {
this.competitorsAndBoats = competitorsAndBoats;
}
public RaceWithCompetitorsAndBoatsDTO(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, BoatDTO> competitorsAndBoats) {
super(raceIdentifier);
this.competitorsAndBoats = competitorsAndBoats;
}
public Map<CompetitorDTO, BoatDTO> getCompetitorsAndBoats() {
return competitorsAndBoats;
}
@@ -41,7 +41,9 @@ import com.sap.sse.mongodb.MongoDBService;
import com.sap.sse.security.Action;
import com.sap.sse.security.ActionWithResult;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.shared.OwnershipAnnotation;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
import com.sap.sse.security.shared.impl.Ownership;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.shared.impl.UserGroup;
@@ -94,9 +96,8 @@ public abstract class AbstractJaxRsApiTest {
}
return null;
}
}).when(securityService).setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(ActionWithResult.class));
}).when(securityService).setOwnershipCheckPermissionForObjectCreationAndRevertOnError(Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(ActionWithResult.class));
Mockito.doReturn(true).when(securityService)
.hasCurrentUserReadPermission(Mockito.any(WithQualifiedObjectIdentifier.class));
@@ -105,6 +106,11 @@ public abstract class AbstractJaxRsApiTest {
Mockito.doReturn(true).when(fakeSubject).isAuthenticated();
OwnershipAnnotation mockedOwnership = Mockito.mock(OwnershipAnnotation.class);
Ownership ownership = Mockito.mock(Ownership.class);
Mockito.doReturn(mockedOwnership).when(securityService).getOwnership(Mockito.any());
Mockito.doReturn(ownership).when(mockedOwnership).getAnnotation();
eventsResource = spyResource(new DummyEventsRessource());
doReturn(getSecurityService()).when(eventsResource).getSecurityService();
@@ -42,10 +42,12 @@ import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.CompetitorRegistrationType;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.security.ActionWithResult;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.UserImpl;
import com.sap.sse.security.shared.Account;
@@ -82,17 +84,34 @@ public class RegattasResourceTest extends AbstractJaxRsApiTest {
Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true,
fleets, raceColumnNames, /* trackedRegattaRegistry */null);
series.add(testSeries);
racingEventService.createRegatta(closedRegattaName, boatClassName,
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
/* registrationLinkSecret */ null, startDate, endDate, UUID.randomUUID(), series, /* persistent */ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null,
/* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */ true,
/* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
racingEventService.createRegatta(openRegattaName, boatClassName, /* canBoatsOfCompetitorsChangePerRace */ true,
CompetitorRegistrationType.OPEN_MODERATED, secret, startDate, endDate, UUID.randomUUID(), series,
/* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null,
/* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */ true,
/* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
final UUID closedRegattaUuid = UUID.randomUUID();
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(SecuredDomainType.REGATTA,
closedRegattaUuid.toString(), closedRegattaName, new ActionWithResult<Regatta>() {
@Override
public Regatta run() throws Exception {
return racingEventService.createRegatta(closedRegattaName, boatClassName,
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
/* registrationLinkSecret */ null, startDate, endDate, closedRegattaUuid, series,
/* persistent */ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null,
/* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */ true,
/* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
}
});
final UUID openRegattaUuid = UUID.randomUUID();
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(SecuredDomainType.REGATTA,
openRegattaUuid.toString(), openRegattaName, new ActionWithResult<Regatta>() {
@Override
public Regatta run() throws Exception {
return racingEventService.createRegatta(openRegattaName, boatClassName,
/* canBoatsOfCompetitorsChangePerRace */ true,
CompetitorRegistrationType.OPEN_MODERATED, secret, startDate, endDate,
UUID.randomUUID(), series, /* persistent */ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null,
/* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */ true,
/* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
}
});
testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null);
testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
@@ -762,32 +762,38 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
final Waypoint result;
if (when != null) {
final Iterable<Pair<Position, SpeedWithBearing>> positionsAndCogsAndSogs = getPositionsAndCogsAndSogs(trackedRace, when, extrapolate);
final Bearing averageCourse = getAverageCourse(positionsAndCogsAndSogs);
final Bearing fromStartBoatToPinEnd = averageCourse.add(new DegreeBearingImpl(-90));
final Pair<Position, Position> leftmostAndRightmostPositionsAtStart = getPositionsFarthestAheadAndFurthestBack(positionsAndCogsAndSogs,
averageCourse.add(new DegreeBearingImpl(90)));
final Position farthestAhead = getPositionsFarthestAheadAndFurthestBack(positionsAndCogsAndSogs, averageCourse).getA();
if (leftmostAndRightmostPositionsAtStart.getA() != null && leftmostAndRightmostPositionsAtStart.getB() != null &&
farthestAhead != null) {
final Position startBoatPosition = leftmostAndRightmostPositionsAtStart.getB().translateGreatCircle(
fromStartBoatToPinEnd.reverse(), LINE_MARGIN);
final Position pinEndPosition = leftmostAndRightmostPositionsAtStart.getA().translateGreatCircle(
fromStartBoatToPinEnd, LINE_MARGIN);
final Mark startBoat = getService().getBaseDomainFactory().getOrCreateMark(UUID.randomUUID(), "Auto "+waypointName+" Boat");
RegattaLogDefineMarkEventImpl defineStartBoatEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(),
getService().getServerAuthor(), when, UUID.randomUUID(), startBoat);
regattaLog.add(defineStartBoatEvent);
final Mark pinEnd = getService().getBaseDomainFactory().getOrCreateMark(UUID.randomUUID(), "Auto "+waypointName+" Pin End");
RegattaLogDefineMarkEventImpl definePinEndEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(),
getService().getServerAuthor(), when, UUID.randomUUID(), pinEnd);
regattaLog.add(definePinEndEvent);
getRaceLogTrackingAdapter().pingMark(regattaLog, startBoat, new GPSFixImpl(startBoatPosition, when), getService());
getRaceLogTrackingAdapter().pingMark(regattaLog, pinEnd, new GPSFixImpl(pinEndPosition, when), getService());
final ControlPoint startLineControlPoint = getService().getBaseDomainFactory().getOrCreateControlPointWithTwoMarks(
UUID.randomUUID(), "Auto "+waypointName+" Line", pinEnd, startBoat);
result = getService().getBaseDomainFactory().createWaypoint(startLineControlPoint, PassingInstruction.Line);
final Bearing averageCourse = getAverageCourse(positionsAndCogsAndSogs); // may return null, e.g., if COG/SOG information is missing
if (averageCourse != null) {
final Bearing fromStartBoatToPinEnd = averageCourse.add(new DegreeBearingImpl(-90));
final Pair<Position, Position> leftmostAndRightmostPositionsAtStart = getPositionsFarthestAheadAndFurthestBack(positionsAndCogsAndSogs,
averageCourse.add(new DegreeBearingImpl(90)));
final Position farthestAhead = getPositionsFarthestAheadAndFurthestBack(positionsAndCogsAndSogs, averageCourse).getA();
if (leftmostAndRightmostPositionsAtStart.getA() != null && leftmostAndRightmostPositionsAtStart.getB() != null &&
farthestAhead != null) {
final Position startBoatPosition = leftmostAndRightmostPositionsAtStart.getB().translateGreatCircle(
fromStartBoatToPinEnd.reverse(), LINE_MARGIN);
final Position pinEndPosition = leftmostAndRightmostPositionsAtStart.getA().translateGreatCircle(
fromStartBoatToPinEnd, LINE_MARGIN);
final Mark startBoat = getService().getBaseDomainFactory().getOrCreateMark(UUID.randomUUID(), "Auto "+waypointName+" Boat");
RegattaLogDefineMarkEventImpl defineStartBoatEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(),
getService().getServerAuthor(), when, UUID.randomUUID(), startBoat);
regattaLog.add(defineStartBoatEvent);
final Mark pinEnd = getService().getBaseDomainFactory().getOrCreateMark(UUID.randomUUID(), "Auto "+waypointName+" Pin End");
RegattaLogDefineMarkEventImpl definePinEndEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(),
getService().getServerAuthor(), when, UUID.randomUUID(), pinEnd);
regattaLog.add(definePinEndEvent);
getRaceLogTrackingAdapter().pingMark(regattaLog, startBoat, new GPSFixImpl(startBoatPosition, when), getService());
getRaceLogTrackingAdapter().pingMark(regattaLog, pinEnd, new GPSFixImpl(pinEndPosition, when), getService());
final ControlPoint startLineControlPoint = getService().getBaseDomainFactory().getOrCreateControlPointWithTwoMarks(
UUID.randomUUID(), "Auto "+waypointName+" Line", pinEnd, startBoat);
result = getService().getBaseDomainFactory().createWaypoint(startLineControlPoint, PassingInstruction.Line);
} else {
result = null;
}
} else {
result = null;
logger.warning(()->"COG/SOG information for race "+trackedRace.getRace().getName()+" at "+when+
" missing. Cannot construct a line for waypoint "+waypointName+" enclosing tracks with unknown course.");
}
} else {
result = null;
@@ -139,6 +139,7 @@ import com.sap.sse.common.util.RoundingUtil;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
import com.sap.sse.security.ActionWithResult;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.shared.OwnershipAnnotation;
import com.sap.sse.security.shared.impl.User;
@Path("/v1/regattas")
@@ -370,7 +371,8 @@ public class RegattasResource extends AbstractSailingServerResource {
final User user = getSecurityService().getCurrentUser();
Response response;
final Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
OwnershipAnnotation regattaOwnerShipAnnotation = getSecurityService().getOwnership(regatta.getIdentifier());
if (regatta == null || regattaOwnerShipAnnotation == null) {
return getBadRegattaErrorResponse(regattaName);
}
boolean registerCompetitor = false;
@@ -478,20 +480,37 @@ public class RegattasResource extends AbstractSailingServerResource {
final String name = eCompetitorName;
final String shortName = eCompetitorShortName;
final String email = eCompetitorEmail;
final CompetitorWithBoat competitor = getSecurityService()
.setOwnershipCheckPermissionForObjectCreationAndRevertOnError(SecuredDomainType.COMPETITOR,
competitorUuid.toString(), name, new ActionWithResult<CompetitorWithBoat>() {
final CompetitorWithBoat competitor;
if (subject.isAuthenticated()) {
competitor = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.COMPETITOR, competitorUuid.toString(), name,
new ActionWithResult<CompetitorWithBoat>() {
@Override
public CompetitorWithBoat run() throws Exception {
return getService().getCompetitorAndBoatStore().getOrCreateCompetitorWithBoat(
competitorUuid, name, shortName, color,
email, flagImageURI, team, timeOnTimeFactor,
competitorUuid, name, shortName, color, email, flagImageURI, team,
timeOnTimeFactor,
timeOnDistanceAllowancePerNauticalMileAsMillis == null ? null
: new MillisecondsDurationImpl(
timeOnDistanceAllowancePerNauticalMileAsMillis),
searchTag, boat);
}
});
} else {
competitor = getService().getCompetitorAndBoatStore().getOrCreateCompetitorWithBoat(competitorUuid,
name, shortName, color, email, flagImageURI, team, timeOnTimeFactor,
timeOnDistanceAllowancePerNauticalMileAsMillis == null ? null
: new MillisecondsDurationImpl(timeOnDistanceAllowancePerNauticalMileAsMillis),
searchTag, boat);
getSecurityService().setOwnership(competitor.getIdentifier(),
(User) regattaOwnerShipAnnotation.getAnnotation().getUserOwner(),
regattaOwnerShipAnnotation.getAnnotation().getTenantOwner(), name);
if (getSecurityService().getOwnership(boat.getIdentifier()) == null) {
getSecurityService().setOwnership(boat.getIdentifier(),
(User) regattaOwnerShipAnnotation.getAnnotation().getUserOwner(),
regattaOwnerShipAnnotation.getAnnotation().getTenantOwner(), name);
}
}
regatta.registerCompetitor(competitor);
response = Response.ok(CompetitorJsonSerializer.create().serialize(competitor).toJSONString())
.header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
@@ -538,7 +557,9 @@ public class RegattasResource extends AbstractSailingServerResource {
private DynamicBoat createBoat(String name, String boatClassName, String sailId) {
final UUID boatUUID = UUID.randomUUID();
DynamicBoat boat = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
final DynamicBoat boat;
if (SecurityUtils.getSubject().isAuthenticated()) {
boat = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.BOAT, boatUUID.toString(), name, new ActionWithResult<DynamicBoat>() {
@Override
@@ -547,6 +568,10 @@ public class RegattasResource extends AbstractSailingServerResource {
.getOrCreateBoatClass(boatClassName, /* typicallyStartsUpwind */ true), sailId);
}
});
} else {
boat = new BoatImpl(boatUUID, name, getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName,
/* typicallyStartsUpwind */ true), sailId);
}
return boat;
}
@@ -995,6 +995,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
securityService.assumeOwnershipMigrated(SecuredDomainType.MEDIA_TRACK.getName(),
SecuredDomainType.getAllInstances());
for (Competitor competitor : getCompetitorAndBoatStore().getAllCompetitors()) {
securityService.migrateOwnership(competitor, SecuredDomainType.getAllInstances());
}
for (Boat boat : getCompetitorAndBoatStore().getBoats()) {
securityService.migrateOwnership(boat, SecuredDomainType.getAllInstances());
}
}
private void loadRaceIDToRegattaAssociations() {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -8,6 +8,9 @@ public interface IconResources extends ClientBundle {
public static final IconResources INSTANCE = GWT.create(IconResources.class);
@Source("images/change-acl.png")
ImageResource changeACLIcon();
@Source("images/change-ownership.png")
ImageResource changeOwnershipIcon();
@@ -149,4 +149,10 @@ public interface UserManagementService extends RemoteService {
public Pair<UserDTO, UserDTO> verifySocialUser(CredentialDTO credential) throws OAuthException;
OwnershipAnnotationDTO getOwnership(QualifiedObjectIdentifier idOfOwnedObject);
AccessControlListDTO overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
AccessControlListDTO acl) throws UnauthorizedException;
AccessControlListDTO getAccessControlListWithoutPruning(QualifiedObjectIdentifier idOfAccessControlledObject)
throws UnauthorizedException;
}
@@ -35,6 +35,12 @@ public interface UserManagementServiceAsync {
void updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
Map<String, Set<String>> permissionStrings, AsyncCallback<AccessControlListDTO> callback);
void getAccessControlListWithoutPruning(QualifiedObjectIdentifier idOfAccessControlledObject,
AsyncCallback<AccessControlListDTO> updateAclAsyncCallback);
void overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, AccessControlListDTO acl,
AsyncCallback<AccessControlListDTO> updateAclAsyncCallback);
void addToAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, String permission, String name,
AsyncCallback<AccessControlListDTO> callback);
@@ -0,0 +1,103 @@
package com.sap.sse.security.ui.client.component;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.ui.client.UserService;
/**
* Panel where several buttons can be added which are either {@link #addUnsecuredAction(String, Command) unsecured} or
* restricted for users with {@link #addCreateAction(String, Command) create} and /or
* {@link #addRemoveAction(String, Command) remove} permissions. The visibility of secured buttons depend on the
* permissions of the currently logged-in user and changes dynamically.
*/
public class AccessControlledButtonPanel extends Composite {
private final HorizontalPanel panel = new HorizontalPanel();
private final Map<Button, Supplier<Boolean>> buttonToPermissions = new HashMap<>();
private final Supplier<Boolean> createPermissionCheck, removePermissionCheck;
private final BiConsumer<Button, Supplier<Boolean>> visibilityUpdater = (btn, check) -> btn.setVisible(check.get());
/**
* Creates an {@link AccessControlledButtonPanel} instance for the given {@link HasPermissions type} using the
* provided {@link UserService} to check permissions and register for user status changes.
*
* @param userService
* the {@link UserService} to check permissions and register for user status changes
* @param type
* the {@link HasPermissions} representing the type of objects to be secured by this panel
*/
public AccessControlledButtonPanel(final UserService userService, final HasPermissions type) {
this.createPermissionCheck = () -> userService.hasCurrentUserPermissionToCreateObjectOfType(type);
this.removePermissionCheck = () -> userService.hasCurrentUserPermissionToDeleteAnyObjectOfType(type);
userService.addUserStatusEventHandler((user, preAuth) -> buttonToPermissions.forEach(visibilityUpdater), true);
initWidget(panel);
}
/**
* Adds an unsecured action button, which's visibility is independent from the current user's permissions.
*
* @param text
* the {@link String text} to show on the button
* @param callback
* the {@link Command callback} to execute on button click
* @return the created {@link Button} instance
*/
public Button addUnsecuredAction(final String text, final Command callback) {
return addAction(text, () -> true, callback);
}
/**
* Adds an secured action button, which is only visible if the current user has the
* {@link UserService#hasCurrentUserPermissionToCreateObjectOfType(HasPermissions) create permission} for the
* {@link HasPermissions type} provided in this {@link AccessControlledButtonPanel}'s constructor.
*
* @param text
* the {@link String text} to show on the button
* @param callback
* the {@link Command callback} to execute on button click, if permission is granted
* @return the created {@link Button} instance
*/
public Button addCreateAction(final String text, final Command callback) {
return addAction(text, createPermissionCheck, callback);
}
/**
* Adds an secured action button, which is only visible if the current user has any
* {@link UserService#hasCurrentUserPermissionToDeleteAnyObjectOfType(HasPermissions) delete permission} for the
* {@link HasPermissions type} provided in this {@link AccessControlledButtonPanel}'s constructor.
*
* @param text
* the {@link String text} to show on the button
* @param callback
* the {@link Command callback} to execute on button click, if permission is granted
* @return the created {@link Button} instance
*/
public Button addRemoveAction(final String text, final Command callback) {
return addAction(text, removePermissionCheck, callback);
}
private Button addAction(final String text, final Supplier<Boolean> permissionCheck, final Command callback) {
final Button button = new Button(text, (ClickHandler) event -> {
if (permissionCheck.get()) {
callback.execute();
}
});
this.buttonToPermissions.put(button, permissionCheck);
button.getElement().getStyle().setMarginRight(5, Unit.PX);
this.panel.add(button);
this.visibilityUpdater.accept(button, permissionCheck);
return button;
}
}
@@ -23,7 +23,8 @@ public class DefaultActionsImagesBarCell extends ImagesBarCell {
@Override
protected Iterable<ImageSpec> getImageSpecs() {
return Arrays.asList(getUpdateImageSpec(), getDeleteImageSpec(), getChangeOwnershipImageSpec());
return Arrays.asList(getUpdateImageSpec(), getDeleteImageSpec(), getChangeOwnershipImageSpec(),
getChangeACLImageSpec());
}
/**
@@ -60,8 +61,8 @@ public class DefaultActionsImagesBarCell extends ImagesBarCell {
* @return {@link ImageSpec} for {@link DefaultActions#CHANGE_ACL change acl} action
*/
protected ImageSpec getChangeACLImageSpec() {
return new ImageSpec(ACTION_CHANGE_OWNERSHIP, stringMessages.actionChangeOwnership(),
IconResources.INSTANCE.changeOwnershipIcon());
return new ImageSpec(ACTION_CHANGE_ACL, stringMessages.actionChangeACL(),
IconResources.INSTANCE.changeACLIcon());
}
}
@@ -0,0 +1,23 @@
.allowedActionsTable {
margin-right: 5px;
margin-left: 5px;
margin-top: 10px;
display: inline;
}
.deniedActionsTable {
margin-right: 5px;
margin-left: 5px;
margin-top: 10px;
display: inline;
}
.additionalWidget {
margin-left: 5px;
margin-right: 5px;
}
.userGroupTextBox {
min-width: 200px;
margin-bottom: 5px;
}
@@ -0,0 +1,26 @@
package com.sap.sse.security.ui.client.component.editacl;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
/**
* Resources used by the authentication framework.
*/
public interface AclDialogResources extends ClientBundle {
public static final AclDialogResources INSTANCE = GWT.create(AclDialogResources.class);
@Source("AclDialogResources.gss")
LocalCss css();
public interface LocalCss extends CssResource {
String allowedActionsTable();
String deniedActionsTable();
String additionalWidget();
String userGroupTextBox();
}
}
@@ -0,0 +1,346 @@
package com.sap.sse.security.ui.client.component.editacl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.CellList;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite;
import com.sap.sse.security.shared.HasPermissions.Action;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.UserGroupDTO;
import com.sap.sse.security.shared.impl.AccessControlList;
import com.sap.sse.security.shared.impl.UserGroup;
import com.sap.sse.security.ui.client.UserManagementServiceAsync;
import com.sap.sse.security.ui.client.i18n.StringMessages;
public class AclEditPanel extends Composite {
private static AclEditPanelUiBinder uiBinder = GWT.create(AclEditPanelUiBinder.class);
interface AclEditPanelUiBinder extends UiBinder<Widget, AclEditPanel> {
}
@UiField
FlowPanel userGroupCellListPanelUi;
@UiField
FlowPanel permissionsCellListPanelUi;
private final Button removeUserGroupButtonUi;
private final Button addUserGroupButtonUi;
private final SuggestBox suggestUserGroupUi;
private final StringMessages stringMessages;
private final StringListEditorComposite allowedActionsEditor;
private final StringListEditorComposite deniedActionsEditor;
private final SingleSelectionModel<UserGroupDTO> userGroupSingleSelectionModel = new SingleSelectionModel<>();
private CellList<UserGroupDTO> userGroupList;
// denied actions start with '!'
private Map<UserGroupDTO, Set<String>> userGroupsWithAllowedActions = new HashMap<>();
private Map<UserGroupDTO, Set<String>> userGroupsWithDeniedActions = new HashMap<>();
private UserManagementServiceAsync userManagementService;
public AclEditPanel(UserManagementServiceAsync userManagementService, Action[] availableActions,
StringMessages stringMessages) {
this.stringMessages = stringMessages;
this.userManagementService = userManagementService;
AclDialogResources.INSTANCE.css().ensureInjected();
suggestUserGroupUi = createUserGroupSuggest(userManagementService);
addUserGroupButtonUi = new Button(stringMessages.add());
removeUserGroupButtonUi = new Button(stringMessages.remove());
addUserGroupButtonUi.addClickHandler(e -> onUserGroupAdd(e));
removeUserGroupButtonUi.addClickHandler(e -> onUserGroupRemove(e));
initWidget(uiBinder.createAndBindUi(this));
userGroupList = createUserGroupCellList();
userGroupCellListPanelUi.add(wrapIntoCaptionPanel(userGroupList, stringMessages.userGroups(),
suggestUserGroupUi, addUserGroupButtonUi, removeUserGroupButtonUi));
userGroupSingleSelectionModel.addSelectionChangeHandler(h -> {
onUserGroupsChange();
});
// retrieve set of available action names
final Collection<String> actionNames = new ArrayList<>();
for (Action a : availableActions) {
actionNames.add(a.name());
}
// create action editor for allowed actions
allowedActionsEditor = new StringListEditorComposite(new ArrayList<>(), stringMessages,
com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), actionNames,
stringMessages.allowedActionName());
allowedActionsEditor.addValueChangeHandler(e -> userGroupsWithAllowedActions
.put(userGroupSingleSelectionModel.getSelectedObject(), toSet(e.getValue())));
CaptionPanel allowedActionsPanel = new CaptionPanel(stringMessages.allowedActions());
allowedActionsPanel.add(allowedActionsEditor);
allowedActionsPanel.addStyleName(AclDialogResources.INSTANCE.css().allowedActionsTable());
permissionsCellListPanelUi.add(allowedActionsPanel);
// create action editor for denied actions
deniedActionsEditor = new StringListEditorComposite(new ArrayList<>(), stringMessages,
com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), actionNames,
stringMessages.deniedActionName());
deniedActionsEditor.addValueChangeHandler(e -> userGroupsWithDeniedActions
.put(userGroupSingleSelectionModel.getSelectedObject(), toDeniedActionSet(e.getValue())));
CaptionPanel deniedActionsPanel = new CaptionPanel(stringMessages.deniedActions());
deniedActionsPanel.add(deniedActionsEditor);
deniedActionsPanel.addStyleName(AclDialogResources.INSTANCE.css().deniedActionsTable());
permissionsCellListPanelUi.add(deniedActionsPanel);
}
/** Called when the selected {@link UserGroup} changes. */
private void onUserGroupsChange() {
removeUserGroupButtonUi.setEnabled(userGroupSingleSelectionModel.getSelectedObject() != null);
}
/** @return UI element for selection of {@link UserGroup} elements. */
private SuggestBox createUserGroupSuggest(UserManagementServiceAsync userManagementService) {
final MultiWordSuggestOracle userGroupOracle = new MultiWordSuggestOracle();
userManagementService.getUserGroups(new AsyncCallback<Collection<UserGroupDTO>>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(Collection<UserGroupDTO> result) {
final List<String> suggestionList = result.stream().map(UserGroupDTO::getName)
.collect(Collectors.toList());
userGroupOracle.clear();
userGroupOracle.addAll(suggestionList);
userGroupOracle.setDefaultSuggestionsFromText(suggestionList);
}
});
TextBox textBox = new TextBox();
textBox.getElement().setPropertyString("placeholder", stringMessages.enterUserGroupName());
SuggestBox suggestBox = new SuggestBox(userGroupOracle, textBox);
suggestBox.addStyleName(AclDialogResources.INSTANCE.css().userGroupTextBox());
return suggestBox;
}
/** @return the UI element for visualizing {@link UserGroup} elements. */
private CellList<UserGroupDTO> createUserGroupCellList() {
final CellList<UserGroupDTO> userGroupCellList = new CellList<>(new AbstractCell<UserGroupDTO>() {
@Override
public void render(Context context, UserGroupDTO value, SafeHtmlBuilder sb) {
if (value != null) {
sb.appendEscaped(value.getName());
}
}
});
userGroupCellList.setSelectionModel(userGroupSingleSelectionModel);
userGroupSingleSelectionModel
.addSelectionChangeHandler(e -> updateActionEditors(userGroupSingleSelectionModel.getSelectedObject()));
return userGroupCellList;
}
/**
* Updates the {@link #allowedActionsEditor} and {@link #deniedActionsEditor} when the selected UserGroup changed.
*/
private void updateActionEditors(UserGroupDTO selectedUserGroup) {
onUserGroupsChange();
allowedActionsEditor.setValue(userGroupsWithAllowedActions.get(selectedUserGroup), false);
deniedActionsEditor
.setValue(removeExclamationMarkFromStrings(userGroupsWithDeniedActions.get(selectedUserGroup)), false);
}
/**
* Wraps the CellList together with a title and additional widgets (e.g. add/remove buttons) into a
* {@link CaptionPanel}.
*/
private CaptionPanel wrapIntoCaptionPanel(CellList<?> cellList, String title, Widget... additionalWidgets) {
cellList.setPageSize(10);
final SimplePager tenantPager = new SimplePager(TextLocation.CENTER, false, /* fast forward step size */ 50,
true);
tenantPager.setDisplay(cellList);
final ScrollPanel tenantPanel = new ScrollPanel(cellList);
final VerticalPanel tenantListWrapper = new VerticalPanel();
// add additional widgets
final FlowPanel additionalWidgetsPanel = new FlowPanel();
for (Widget additionalWidget : additionalWidgets) {
additionalWidgetsPanel.add(additionalWidget);
additionalWidget.addStyleName(AclDialogResources.INSTANCE.css().additionalWidget());
}
tenantListWrapper.add(additionalWidgetsPanel);
tenantListWrapper.add(tenantPanel);
tenantListWrapper.add(tenantPager);
final CaptionPanel tenantListCaption = new CaptionPanel(title);
tenantListCaption.add(tenantListWrapper);
return tenantListCaption;
}
/**
* Updates the ACL edited in the EditAclDialog. Splits combinedActions retrieved by
* {@link AccessControlList#getActionsByUserGroup()} into {@link #allowedActionsEditor} and
* {@link #deniedActionsEditor}.
*/
public void updateAcl(AccessControlListDTO acl) {
final Map<UserGroupDTO, Set<String>> combinedActions = (acl != null)
? acl.getActionsByUserGroup() != null ? new HashMap<>(acl.getActionsByUserGroup()) : new HashMap<>()
: new HashMap<>();
for (Map.Entry<UserGroupDTO, Set<String>> combinedAction : combinedActions.entrySet()) {
final Set<String> allowedActions = new HashSet<>();
final Set<String> deniedActions = new HashSet<>();
for (String action : combinedAction.getValue()) {
if (action.startsWith("!")) {
deniedActions.add(action);
} else {
allowedActions.add(action);
}
}
userGroupsWithAllowedActions.put(combinedAction.getKey(), allowedActions);
userGroupsWithDeniedActions.put(combinedAction.getKey(), deniedActions);
}
refreshUi();
}
/**
* Updates the UI by refreshing the {@link #userGroupList} and selects an element to trigger the update of
* {@link #allowedActionsEditor} and {@link #deniedActionsEditor}.
*/
private void refreshUi() {
final Set<UserGroupDTO> combinedKeySet = new HashSet<>();
combinedKeySet.addAll(userGroupsWithAllowedActions.keySet());
combinedKeySet.addAll(userGroupsWithDeniedActions.keySet());
userGroupList.setRowCount(combinedKeySet.size(), true);
userGroupList.setRowData(0, new ArrayList<>(combinedKeySet));
// select an element
if (combinedKeySet.size() > 0) {
userGroupSingleSelectionModel.setSelected(combinedKeySet.iterator().next(), true);
}
}
/** Called when the user clicks on the 'Add' button */
private void onUserGroupAdd(ClickEvent e) {
final String userGroupName = suggestUserGroupUi.getValue();
// get UserGroup object corresponding to user group name
userManagementService.getUserGroupByName(userGroupName, new AsyncCallback<UserGroupDTO>() {
@Override
public void onFailure(Throwable caught) {
Notification.notify(stringMessages.errorMessageUserGroupNameNotFound(userGroupName),
NotificationType.ERROR);
}
@Override
public void onSuccess(UserGroupDTO result) {
if (result == null) {
Notification.notify(stringMessages.errorMessageUserGroupNameNotFound(userGroupName),
NotificationType.ERROR);
} else {
Notification.notify(stringMessages.successMessageAddedUserGroup(userGroupName),
NotificationType.SUCCESS);
userGroupsWithAllowedActions.put(result, new HashSet<>());
userGroupsWithDeniedActions.put(result, new HashSet<>());
refreshUi();
suggestUserGroupUi.setText("");
userGroupSingleSelectionModel.setSelected(result, true);
}
}
});
}
/** Called when the user clicks on the 'Remove' button */
private void onUserGroupRemove(ClickEvent e) {
UserGroupDTO selectedObject = userGroupSingleSelectionModel.getSelectedObject();
if (selectedObject != null) {
userGroupsWithAllowedActions.remove(selectedObject);
userGroupsWithDeniedActions.remove(selectedObject);
Notification.notify(stringMessages.successMessageRemovedUserGroup(selectedObject.getName()),
NotificationType.SUCCESS);
refreshUi();
}
}
/** Merges {@link #userGroupsWithAllowedActions} and {@link #userGroupsWithDeniedActions}. */
public Map<UserGroupDTO, Set<String>> getUserGroupsWithCombinedActions() {
final Map<UserGroupDTO, Set<String>> combinedActions = new HashMap<>(userGroupsWithAllowedActions);
for (Map.Entry<UserGroupDTO, Set<String>> actionEntry : userGroupsWithDeniedActions.entrySet()) {
if (combinedActions.containsKey(actionEntry.getKey())) {
final Set<String> set = combinedActions.get(actionEntry.getKey());
set.addAll(actionEntry.getValue());
} else {
combinedActions.put(actionEntry.getKey(), actionEntry.getValue());
}
}
return combinedActions;
}
/** Converts an {@link Iterable} into a {@link Set}. */
private <T> Set<T> toSet(Iterable<T> iter) {
Set<T> resultSet;
if (iter instanceof Set) {
resultSet = (Set<T>) iter;
} else {
resultSet = new HashSet<>();
for (T t : iter) {
resultSet.add(t);
}
}
return resultSet;
}
/**
* Converts an {@link Iterable} into a {@link Set} and adds a '!' in front of each String to mark the action as
* denied.
*/
private Set<String> toDeniedActionSet(Iterable<String> iter) {
final Set<String> set = new HashSet<>();
for (String s : iter) {
set.add(s.startsWith("!") ? s : "!" + s);
}
return set;
}
private Set<String> removeExclamationMarkFromStrings(Iterable<String> iter) {
final Set<String> set = new HashSet<>();
for (String s : iter) {
set.add(s.startsWith("!") ? s.substring(1) : s);
}
return set;
}
}
@@ -0,0 +1,7 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<g:HTMLPanel>
<g:FlowPanel ui:field="userGroupCellListPanelUi" />
<g:FlowPanel ui:field="permissionsCellListPanelUi" />
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,162 @@
package com.sap.sse.security.ui.client.component.editacl;
import static com.sap.sse.gwt.client.Notification.NotificationType.ERROR;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.common.Named;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.Action;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.SecuredDTO;
import com.sap.sse.security.ui.client.UserManagementServiceAsync;
import com.sap.sse.security.ui.client.component.editacl.EditACLDialog.AclDialogResult;
import com.sap.sse.security.ui.client.i18n.StringMessages;
public class EditACLDialog extends DataEntryDialog<AclDialogResult> {
private final AclEditPanel aclEditPanel;
static class AclDialogResult {
private final AccessControlListDTO acl;
private AclDialogResult(final AccessControlListDTO acl) {
this.acl = acl;
}
public AccessControlListDTO getAcl() {
return acl;
}
}
private static class Validator implements DataEntryDialog.Validator<AclDialogResult> {
@Override
public String getErrorMessage(AclDialogResult valueToValidate) {
// nothing to validate since user input is already validated in aclPanel
return null;
}
}
private EditACLDialog(UserManagementServiceAsync userManagementService,
QualifiedObjectIdentifier qualifiedObjectIdentifier, Action[] availableActions,
StringMessages stringMessages, DialogCallback<AclDialogResult> callback) {
super(stringMessages.acl(), stringMessages.editACLForObject(qualifiedObjectIdentifier.toString()),
stringMessages.ok(), stringMessages.cancel(), new Validator(), callback);
aclEditPanel = new AclEditPanel(userManagementService, availableActions, stringMessages);
userManagementService.getAccessControlListWithoutPruning(qualifiedObjectIdentifier,
new AsyncCallback<AccessControlListDTO>() {
@Override
public void onFailure(Throwable caught) {
Notification.notify(caught.getMessage(), NotificationType.ERROR);
}
@Override
public void onSuccess(AccessControlListDTO result) {
aclEditPanel.updateAcl(result);
}
});
}
@Override
protected Widget getAdditionalWidget() {
return aclEditPanel;
}
@Override
protected AclDialogResult getResult() {
return new AclDialogResult(new AccessControlListDTO(aclEditPanel.getUserGroupsWithCombinedActions()));
}
/**
* Creates a new {@link DialogConfig dialog configuration} instance which can be (re-)used to
* {@link DialogConfig#openDialog(Named) open} a {@link EditACLDialog dialog}.
*
* @param userManagementService
* {@link UserManagementServiceAsync} to use to set the secured object's ownership
* @param type
* {@link SecuredDomainType} specifying the type of required permissions to modify the secured object
* @param typeRelativeIdFactory
* {@link Function factory} to get a {@link String type relative identifier} for the secured object
* @param updateCallback
* {@link Consumer callback} to execute when the dialog is confirmed and ownership update succeeded
* @param errorCallback
* {@link Consumer callback} to execute when the dialog is confirmed and ownership update fails
*/
public static <T extends Named & SecuredDTO> DialogConfig<T> create(
final UserManagementServiceAsync userManagementService, final HasPermissions type,
final Function<T, String> typeRelativeIdFactory, final Consumer<T> updateCallback,
final StringMessages stringMessages) {
return new DialogConfig<>(userManagementService, type, typeRelativeIdFactory, updateCallback, stringMessages);
}
public static class DialogConfig<T extends Named & SecuredDTO> {
private final UserManagementServiceAsync userManagementService;
private final Consumer<T> updateCallback;
private final Function<T, QualifiedObjectIdentifier> identifierFactory;
private final Function<T, Action[]> availableActionsFactory;
private final StringMessages stringMessages;
private DialogConfig(final UserManagementServiceAsync userManagementService, final HasPermissions type,
final Function<T, String> idFactory, final Consumer<T> updateCallback,
final StringMessages stringMessages) {
this.userManagementService = userManagementService;
this.identifierFactory = idFactory.andThen(type::getQualifiedObjectIdentifier);
this.availableActionsFactory = idFactory.andThen(t -> type.getAvailableActions());
this.updateCallback = updateCallback;
this.stringMessages = stringMessages;
}
/**
* Opens a {@link EditACLDialog dialog} to edit ownerships for the provided secured object instance.
*
* @param securedObject
* {@link Named} {@link SecuredObject} instance to edit ownerships for
*/
public void openDialog(final T securedObject) {
new EditACLDialog(userManagementService, identifierFactory.apply(securedObject),
availableActionsFactory.apply(securedObject), StringMessages.INSTANCE,
new EditAclDialogCallback(securedObject)).show();
}
private class EditAclDialogCallback implements DialogCallback<AclDialogResult> {
private final T securedObject;
private EditAclDialogCallback(T securedObject) {
this.securedObject = securedObject;
}
@Override
public void ok(AclDialogResult aclResult) {
final QualifiedObjectIdentifier objectIdentifier = identifierFactory.apply(securedObject);
userManagementService.overrideAccessControlList(objectIdentifier, aclResult.getAcl(),
new AsyncCallback<AccessControlListDTO>() {
@Override
public void onSuccess(AccessControlListDTO result) {
securedObject.setAccessControlList(result);
updateCallback.accept(securedObject);
}
@Override
public final void onFailure(Throwable caught) {
Notification.notify(stringMessages.errorUpdatingAcl(securedObject.getName()), ERROR);
}
});
}
@Override
public final void cancel() {
}
}
}
}
@@ -2,6 +2,7 @@ package com.sap.sse.security.ui.client.i18n;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
import com.google.gwt.safehtml.shared.SafeHtml;
@DefaultLocale("en")
public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
@@ -152,4 +153,15 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
String pleaseWaitUntilUserGroupNameIsResolved();
String userNotFound(String username);
String usergroupNotFound(String userGroupName);
String actionChangeACL();
String editACLForObject(String objectName);
String acl();
String errorUpdatingAcl(String name);
String allowedActionName();
String deniedActionName();
String errorMessageUserGroupNameNotFound(String name);
String successMessageAddedUserGroup(String name);
String successMessageRemovedUserGroup(String name);
SafeHtml allowedActions();
SafeHtml deniedActions();
}
@@ -159,4 +159,15 @@ errorObtainingUserGroup=Error obtaining user group: {0}
pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved
pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved
userNotFound=User {0} not found
usergroupNotFound=User group {0} not found
usergroupNotFound=User group {0} not found
actionChangeACL=Change ACL
editACLForObject=Edit ACL for ''{0}''
acl=ACL
errorUpdatingAcl=Error updating ACL: {0}.
allowedActionName=Allowed action name
deniedActionName=Denied action name
errorMessageUserGroupNameNotFound=Did not find user group by name {0}.
successMessageAddedUserGroup=Added user group {0}.
successMessageRemovedUserGroup=Removed user group {0}.
allowedActions=Allowed Actions
deniedActions=Denied Actions
@@ -158,4 +158,15 @@ errorObtainingUserGroup=Fehler beim Nachschlagen der Benutzergruppe: {0}
pleaseWaitUntilUsernameIsResolved=Bitte warten, bis der Benutzername ausgelöst wurde
pleaseWaitUntilUserGroupNameIsResolved=Bitte warten, bis der Benutzergruppen-Name aufgelöst wurde
userNotFound=Benutzer {0} nicht gefunden
usergroupNotFound=Benutzergruppe {0} nicht gefunden
usergroupNotFound=Benutzergruppe {0} nicht gefunden
actionChangeACL=ACL ändern
editACLForObject=ACL für ''{0}'' ändern
acl=ACL
errorUpdatingAcl=Fehler beim Aktualisieren der ACL: {0}.
allowedActionName=Erlaubter Aktionsname
deniedActionName=Verweigerter Aktionsname
errorMessageUserGroupNameNotFound=Benutzergruppe nicht gefunden: {0}.
successMessageAddedUserGroup=Benutzergruppe hinzugefügt: {0}.
successMessageRemovedUserGroup=Benutzergruppe entfernt: {0}.
allowedActions=Erlabute Aktionen
deniedActions=Verweigerte Aktionen
@@ -40,6 +40,7 @@ import com.sap.sse.gwt.client.celltable.TableWrapper;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.shared.WildcardPermission;
import com.sap.sse.security.shared.dto.RoleDTO;
import com.sap.sse.security.shared.dto.UserDTO;
@@ -50,8 +51,8 @@ import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog.DialogConfig;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
import com.sap.sse.security.ui.client.component.editacl.EditACLDialog;
import com.sap.sse.security.ui.client.i18n.StringMessages;
import com.sap.sse.security.ui.shared.SuccessInfo;
@@ -186,9 +187,16 @@ extends TableWrapper<UserDTO, S, StringMessages, TR> {
});
}
});
final DialogConfig<UserDTO> config = EditOwnershipDialog.create(userService.getUserManagementService(), type,
final EditOwnershipDialog.DialogConfig<UserDTO> configOwnership = EditOwnershipDialog.create(
userService.getUserManagementService(), type,
idFactory, user -> refreshUserList((Callback<Iterable<UserDTO>, Throwable>) null), stringMessages);
userActionColumn.addAction(ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, config::openDialog);
final EditACLDialog.DialogConfig<UserDTO> configACL = EditACLDialog.create(
userService.getUserManagementService(), type,
idFactory, user -> user.getAccessControlList(), stringMessages);
userActionColumn.addAction(ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, configOwnership::openDialog);
userActionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL,
u -> configACL.openDialog(u));
filterField = new LabeledAbstractFilterablePanel<UserDTO>(new Label(stringMessages.filterUsers()),
new ArrayList<UserDTO>(), dataProvider) {
@@ -275,7 +283,7 @@ extends TableWrapper<UserDTO, S, StringMessages, TR> {
users.add(user);
getUserManagementService().updateUserProperties(user.getName(), user.getFullName(),
user.getCompany(), user.getLocale(),
user.getDefaultTenant() != null ? user.getDefaultTenant().getName() : null,
user.getDefaultTenant() != null ? user.getDefaultTenant().getId().toString() : null,
new AsyncCallback<UserDTO>() {
@Override
public void onFailure(Throwable caught) {
@@ -800,4 +800,45 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
public String getOrCreateAccessToken(String username) {
return getSecurityService().getOrCreateAccessToken(username);
}
@Override
public AccessControlListDTO overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
AccessControlListDTO acl) throws UnauthorizedException {
if (SecurityUtils.getSubject()
.isPermitted(idOfAccessControlledObject.getStringPermission(DefaultActions.CHANGE_ACL))) {
Map<UserGroup,Set<String>> aclActionsByGroup = new HashMap<>();
for (Map.Entry<UserGroupDTO, Set<String>> entry : acl.getActionsByUserGroup().entrySet()) {
final UserGroupDTO groupDTO = entry.getKey();
final UserGroup userGroup;
if (groupDTO == null) {
userGroup = null;
} else {
userGroup = getSecurityService().getUserGroup(groupDTO.getId());
}
aclActionsByGroup.put(userGroup, entry.getValue());
}
return securityDTOFactory.createAccessControlListDTO(getSecurityService()
.overrideAccessControlList(idOfAccessControlledObject, aclActionsByGroup));
} else {
throw new UnauthorizedException("Not permitted to update the ACL for a user");
}
}
@Override
public AccessControlListDTO getAccessControlListWithoutPruning(QualifiedObjectIdentifier idOfAccessControlledObject) throws UnauthorizedException {
if (SecurityUtils.getSubject()
.isPermitted(idOfAccessControlledObject.getStringPermission(DefaultActions.CHANGE_ACL))) {
AccessControlListAnnotation accessControlList = getSecurityService().getAccessControlList(idOfAccessControlledObject);
if (accessControlList == null) {
return null;
}
return securityDTOFactory.createAccessControlListDTO(
accessControlList.getAnnotation());
} else {
throw new UnauthorizedException("Not permitted to get the unpruned ACL for a user");
}
}
}
@@ -76,6 +76,9 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
AccessControlList updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, Map<UserGroup, Set<String>> permissionMap);
AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
Map<UserGroup, Set<String>> permissionMap);
/**
* @param name The name of the user group to add
*/
@@ -423,6 +423,13 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
return null;
}
@Override
public AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
Map<UserGroup, Set<String>> permissionMap) {
accessControlStore.removeAccessControlList(idOfAccessControlledObject);
return updateAccessControlList(idOfAccessControlledObject, permissionMap);
}
@Override
public AccessControlList updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, Map<UserGroup, Set<String>> permissionMap) {
if (getAccessControlList(idOfAccessControlledObject) == null) {