diff --git a/configuration/launchhudsonslave-java11 b/configuration/launchhudsonslave-java11 new file mode 100755 index 00000000000..0dc430eef52 --- /dev/null +++ b/configuration/launchhudsonslave-java11 @@ -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 diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java old mode 100755 new mode 100644 index 5142ada18e0..09067905a19 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java @@ -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.

@@ -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()); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/regatta/GetOpenRegattaByRegattaNameAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/regatta/GetOpenRegattaByRegattaNameAction.java index d60eff330ac..ab904ecc50e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/regatta/GetOpenRegattaByRegattaNameAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/regatta/GetOpenRegattaByRegattaNameAction.java @@ -38,7 +38,7 @@ public class GetOpenRegattaByRegattaNameAction implements SailingAction 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 { @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 groupColumn = SecuredDTOOwnerColumn.getGroupOwnerColumn(); + groupColumn.setSortable(true); + final SecuredDTOOwnerColumn userColumn = SecuredDTOOwnerColumn.getUserOwnerColumn(); + userColumn.setSortable(true); + + final HasPermissions type = SecuredDomainType.TRACKED_RACE; + final Function idFactory = race -> race.getTypeRelativeIdentifierAsString(); + final AccessControlledActionsColumn actionsColumn = new AccessControlledActionsColumn<>( + new DefaultActionsImagesBarCell(stringMessages), userService, type, idFactory); + final DialogConfig 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 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 57fb9fc15c6..e694769cd25 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -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) { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java index 3ec6af14f23..49cfd473393 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java @@ -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 eventTable; + private final CellTable eventTable; private final RefreshableMultiSelectionModel refreshableEventSelectionModel; - private ListDataProvider eventListDataProvider; - private List allEvents; - private LabeledAbstractFilterablePanel filterTextbox; - private Button removeEventsButton; + private final ListDataProvider eventListDataProvider; + private final List allEvents; + private final LabeledAbstractFilterablePanel filterTextbox; private final Label noEventsLabel; - private final SimplePanel mainPanel; - private final VerticalPanel panel; - private Iterable availableLeaderboardGroups; public static class AnchorCell extends AbstractCell { @@ -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(); + this.availableLeaderboardGroups = Collections.emptyList(); + this.allEvents = new ArrayList(); - 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(); filterTextbox = new LabeledAbstractFilterablePanel(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 createEventTable(UserDTO user) { @@ -362,17 +329,16 @@ public class EventListComposite extends Composite implements EventsRefresher, Le final SecuredDTOOwnerColumn groupColumn = SecuredDTOOwnerColumn.getGroupOwnerColumn(); final SecuredDTOOwnerColumn userColumn = SecuredDTOOwnerColumn.getUserOwnerColumn(); - final HasPermissions type = SecuredDomainType.EVENT; final Function idFactory = event -> event.id.toString(); final AccessControlledActionsColumn 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 config = EditOwnershipDialog.create(userService.getUserManagementService(), type, + final DialogConfig config = EditOwnershipDialog.create(userService.getUserManagementService(), EVENT, idFactory, event -> fillEvents(), stringMessages); actionsColumn.addAction(EventConfigImagesBarCell.ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, config::openDialog); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index fba05f2afb8..1ef95a2fef6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -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()), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java index 70fd97b0bd2..a36683ed86a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java @@ -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(); this.actionButtonsEnabled = actionButtonsEnabled; createUI(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java index d7c70fc7ed6..48ad38cd935 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java @@ -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() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagInputPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagInputPanel.java index e8eba9a498e..fd1f9b53e85 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagInputPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagInputPanel.java @@ -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 { * false */ 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 { * false */ 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(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index ef130fd60ac..feaed8ace3d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1241,6 +1241,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Map 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 convertToCompetitorDTOs(Iterable iterable) { List 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 result = new ArrayList(); 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> 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 getCompetitors(boolean filterCompetitorsWithBoat, boolean filterCompetitorsWithoutBoat) { - Iterable result; + public Iterable getCompetitors(boolean filterCompetitorsWithBoat, + boolean filterCompetitorsWithoutBoat) { + final List 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 getAllBoats() { - return convertToBoatDTOs(getService().getBaseDomainFactory().getCompetitorAndBoatStore().getBoats()); + List 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 getStandaloneBoats() { - return convertToBoatDTOs(getService().getBaseDomainFactory().getCompetitorAndBoatStore().getStandaloneBoats()); + List 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 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceWithCompetitorsAndBoatsDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceWithCompetitorsAndBoatsDTO.java index fc01a9db971..dd03b609d82 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceWithCompetitorsAndBoatsDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceWithCompetitorsAndBoatsDTO.java @@ -24,11 +24,6 @@ public class RaceWithCompetitorsAndBoatsDTO extends RaceDTO { this.competitorsAndBoats = competitorsAndBoats; } - public RaceWithCompetitorsAndBoatsDTO(RegattaAndRaceIdentifier raceIdentifier, Map competitorsAndBoats) { - super(raceIdentifier); - this.competitorsAndBoats = competitorsAndBoats; - } - public Map getCompetitorsAndBoats() { return competitorsAndBoats; } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java index 8ec82b60347..31dc9191138 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java @@ -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(); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java index 53c853e14d0..b3bf5c69872 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java @@ -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() { + @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() { + @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); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index 988b1718020..0099989c1ae 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -762,32 +762,38 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { final Waypoint result; if (when != null) { final Iterable> positionsAndCogsAndSogs = getPositionsAndCogsAndSogs(trackedRace, when, extrapolate); - final Bearing averageCourse = getAverageCourse(positionsAndCogsAndSogs); - final Bearing fromStartBoatToPinEnd = averageCourse.add(new DegreeBearingImpl(-90)); - final Pair 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 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; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 0c7deb35938..63a3815227f 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -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() { + final CompetitorWithBoat competitor; + if (subject.isAuthenticated()) { + competitor = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError( + SecuredDomainType.COMPETITOR, competitorUuid.toString(), name, + new ActionWithResult() { @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() { @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; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index c32ea3e7779..0a3a8e29d4c 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -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() { diff --git a/java/com.sap.sse.gwt/resources/com/sap/sse/gwt/client/images/change-acl.png b/java/com.sap.sse.gwt/resources/com/sap/sse/gwt/client/images/change-acl.png new file mode 100644 index 00000000000..c938b1c13ad Binary files /dev/null and b/java/com.sap.sse.gwt/resources/com/sap/sse/gwt/client/images/change-acl.png differ diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java index 36441c5caca..65907efc86b 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java @@ -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(); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java index fed261c60cc..11aefe2b2bb 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java @@ -149,4 +149,10 @@ public interface UserManagementService extends RemoteService { public Pair verifySocialUser(CredentialDTO credential) throws OAuthException; OwnershipAnnotationDTO getOwnership(QualifiedObjectIdentifier idOfOwnedObject); + + AccessControlListDTO overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, + AccessControlListDTO acl) throws UnauthorizedException; + + AccessControlListDTO getAccessControlListWithoutPruning(QualifiedObjectIdentifier idOfAccessControlledObject) + throws UnauthorizedException; } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java index 9f80051f08b..84ba92ab358 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java @@ -35,6 +35,12 @@ public interface UserManagementServiceAsync { void updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, Map> permissionStrings, AsyncCallback callback); + void getAccessControlListWithoutPruning(QualifiedObjectIdentifier idOfAccessControlledObject, + AsyncCallback updateAclAsyncCallback); + + void overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, AccessControlListDTO acl, + AsyncCallback updateAclAsyncCallback); + void addToAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, String permission, String name, AsyncCallback callback); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/AccessControlledButtonPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/AccessControlledButtonPanel.java new file mode 100644 index 00000000000..6c549088bce --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/AccessControlledButtonPanel.java @@ -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> buttonToPermissions = new HashMap<>(); + + private final Supplier createPermissionCheck, removePermissionCheck; + private final BiConsumer> 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 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; + } + +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/DefaultActionsImagesBarCell.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/DefaultActionsImagesBarCell.java index 1ef6f9288df..4f504a5adcf 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/DefaultActionsImagesBarCell.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/DefaultActionsImagesBarCell.java @@ -23,7 +23,8 @@ public class DefaultActionsImagesBarCell extends ImagesBarCell { @Override protected Iterable 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()); } } \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.gss b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.gss new file mode 100644 index 00000000000..b61f68bd8bd --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.gss @@ -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; +} \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.java new file mode 100644 index 00000000000..6182ca84321 --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclDialogResources.java @@ -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(); + } +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.java new file mode 100644 index 00000000000..d9117022cf6 --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.java @@ -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 { + } + + @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 userGroupSingleSelectionModel = new SingleSelectionModel<>(); + private CellList userGroupList; + + // denied actions start with '!' + private Map> userGroupsWithAllowedActions = new HashMap<>(); + private Map> 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 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>() { + @Override + public void onFailure(Throwable caught) { + Window.alert(caught.getMessage()); + } + + @Override + public void onSuccess(Collection result) { + final List 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 createUserGroupCellList() { + final CellList userGroupCellList = new CellList<>(new AbstractCell() { + @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> combinedActions = (acl != null) + ? acl.getActionsByUserGroup() != null ? new HashMap<>(acl.getActionsByUserGroup()) : new HashMap<>() + : new HashMap<>(); + + for (Map.Entry> combinedAction : combinedActions.entrySet()) { + final Set allowedActions = new HashSet<>(); + final Set 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 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() { + @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> getUserGroupsWithCombinedActions() { + final Map> combinedActions = new HashMap<>(userGroupsWithAllowedActions); + for (Map.Entry> actionEntry : userGroupsWithDeniedActions.entrySet()) { + if (combinedActions.containsKey(actionEntry.getKey())) { + final Set 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 Set toSet(Iterable iter) { + Set resultSet; + if (iter instanceof Set) { + resultSet = (Set) 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 toDeniedActionSet(Iterable iter) { + final Set set = new HashSet<>(); + for (String s : iter) { + set.add(s.startsWith("!") ? s : "!" + s); + } + return set; + } + + private Set removeExclamationMarkFromStrings(Iterable iter) { + final Set set = new HashSet<>(); + for (String s : iter) { + set.add(s.startsWith("!") ? s.substring(1) : s); + } + return set; + } + +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.ui.xml b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.ui.xml new file mode 100644 index 00000000000..f092d3bc472 --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/AclEditPanel.ui.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/EditACLDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/EditACLDialog.java new file mode 100644 index 00000000000..02f7dae3b45 --- /dev/null +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/editacl/EditACLDialog.java @@ -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 { + + 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 { + @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 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() { + + @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 DialogConfig create( + final UserManagementServiceAsync userManagementService, final HasPermissions type, + final Function typeRelativeIdFactory, final Consumer updateCallback, + final StringMessages stringMessages) { + return new DialogConfig<>(userManagementService, type, typeRelativeIdFactory, updateCallback, stringMessages); + } + + public static class DialogConfig { + private final UserManagementServiceAsync userManagementService; + private final Consumer updateCallback; + private final Function identifierFactory; + private final Function availableActionsFactory; + private final StringMessages stringMessages; + + private DialogConfig(final UserManagementServiceAsync userManagementService, final HasPermissions type, + final Function idFactory, final Consumer 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 { + + 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() { + @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() { + } + } + } + +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java index d3f3d42e9f4..ec403f1f718 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java @@ -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(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties index dff32c42aad..cef601f1213 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties index a198301270d..eae56b277a0 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java index 97746fbbb62..9c0069cdd71 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserTableWrapper.java @@ -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 { }); } }); - final DialogConfig config = EditOwnershipDialog.create(userService.getUserManagementService(), type, + final EditOwnershipDialog.DialogConfig configOwnership = EditOwnershipDialog.create( + userService.getUserManagementService(), type, idFactory, user -> refreshUserList((Callback, Throwable>) null), stringMessages); - userActionColumn.addAction(ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, config::openDialog); + + final EditACLDialog.DialogConfig 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(new Label(stringMessages.filterUsers()), new ArrayList(), dataProvider) { @@ -275,7 +283,7 @@ extends TableWrapper { 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() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index 309ae7341f2..578641ecefc 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -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> aclActionsByGroup = new HashMap<>(); + for (Map.Entry> 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"); + } + } + } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index 01b953e9891..b3fec381eaa 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -76,6 +76,9 @@ public interface SecurityService extends ReplicableWithObjectInputStream> permissionMap); + AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, + Map> permissionMap); + /** * @param name The name of the user group to add */ diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index fc45857ebab..a43ff7dcd7c 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -423,6 +423,13 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat return null; } + @Override + public AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, + Map> permissionMap) { + accessControlStore.removeAccessControlList(idOfAccessControlledObject); + return updateAccessControlList(idOfAccessControlledObject, permissionMap); + } + @Override public AccessControlList updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, Map> permissionMap) { if (getAccessControlList(idOfAccessControlledObject) == null) {