From 087a6fa263f1a923ba5038244c1e807285785e8b Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Thu, 8 Nov 2018 11:28:28 +0100 Subject: [PATCH 01/42] Minor code formatting and class/method visibility changes --- .../client/component/EditOwnershipDialog.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditOwnershipDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditOwnershipDialog.java index 949ed8f1f84..f25b0495577 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditOwnershipDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/EditOwnershipDialog.java @@ -10,6 +10,7 @@ import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; 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; @@ -37,34 +38,39 @@ public class EditOwnershipDialog extends DataEntryDialog private SecurityUser resolvedUser; private UserGroup resolvedUserGroup; - public static class OwnershipDialogResult { + static class OwnershipDialogResult { private final Ownership ownership; private final String username; private final String userGroupName; private final boolean resolvingUsername; private final boolean resolvingUserGroupName; - public OwnershipDialogResult(Ownership ownership, String username, String userGroupName, - boolean resolvingUsername, boolean resolvingUserGroupName) { - super(); + + private OwnershipDialogResult(final Ownership ownership, final String username, final String userGroupName, + final boolean resolvingUsername, final boolean resolvingUserGroupName) { this.ownership = ownership; this.username = username; this.userGroupName = userGroupName; this.resolvingUsername = resolvingUsername; this.resolvingUserGroupName = resolvingUserGroupName; } - public Ownership getOwnership() { + + private Ownership getOwnership() { return ownership; } - public boolean isResolvingUsername() { + + private boolean isResolvingUsername() { return resolvingUsername; } - public boolean isResolvingUserGroupName() { + + private boolean isResolvingUserGroupName() { return resolvingUserGroupName; } - public String getUsername() { + + private String getUsername() { return username; } - public String getUserGroupName() { + + private String getUserGroupName() { return userGroupName; } } @@ -95,7 +101,7 @@ public class EditOwnershipDialog extends DataEntryDialog } } - public EditOwnershipDialog(UserManagementServiceAsync userManagementService, Ownership ownership, + private EditOwnershipDialog(UserManagementServiceAsync userManagementService, Ownership ownership, StringMessages stringMessages, DialogCallback callback) { super(stringMessages.ownership(), stringMessages.editObjectOwnership(), stringMessages.ok(), stringMessages.cancel(), new Validator(stringMessages), callback); @@ -161,7 +167,8 @@ public class EditOwnershipDialog extends DataEntryDialog @Override protected OwnershipDialogResult getResult() { - return new OwnershipDialogResult(new OwnershipImpl(resolvedUser, resolvedUserGroup), usernameBox.getText(), groupnameBox.getText(), resolvingUsername, resolvingUserGroupName); + return new OwnershipDialogResult(new OwnershipImpl(resolvedUser, resolvedUserGroup), usernameBox.getText(), + groupnameBox.getText(), resolvingUsername, resolvingUserGroupName); } /** From 635b3156b9b8a520ffb90b32fdc3803704413d93 Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Thu, 8 Nov 2018 12:20:51 +0100 Subject: [PATCH 02/42] Wrapped security information into separate data transfer object --- .../shared/NamedSecuredObjectDTO.java | 14 +++---- .../shared/SecurityInformationDTO.java | 40 +++++++++++++++++++ .../component/SecuredObjectOwnerColumn.java | 12 +++--- .../security/ui/shared/RoleDefinitionDTO.java | 16 ++++---- .../sap/sse/security/ui/shared/UserDTO.java | 17 ++++---- 5 files changed, 68 insertions(+), 31 deletions(-) create mode 100644 java/com.sap.sse.security.common/src/com/sap/sse/security/shared/SecurityInformationDTO.java diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/NamedSecuredObjectDTO.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/NamedSecuredObjectDTO.java index fa57bec8795..3c79d881c18 100644 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/NamedSecuredObjectDTO.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/NamedSecuredObjectDTO.java @@ -5,10 +5,9 @@ package com.sap.sse.security.shared; */ public class NamedSecuredObjectDTO extends NamedDTO implements SecuredObject { - private static final long serialVersionUID = 4448408121977185408L; + private static final long serialVersionUID = 2642220699434177353L; - private AccessControlList accessControlList; - private Ownership ownership; + private SecurityInformationDTO securityInformation = new SecurityInformationDTO(); protected NamedSecuredObjectDTO() { } @@ -19,22 +18,21 @@ public class NamedSecuredObjectDTO extends NamedDTO implements SecuredObject { @Override public final AccessControlList getAccessControlList() { - return accessControlList; + return securityInformation.getAccessControlList(); } @Override public final Ownership getOwnership() { - return ownership; + return securityInformation.getOwnership(); } @Override public final void setAccessControlList(final AccessControlList accessControlList) { - this.accessControlList = accessControlList; + this.securityInformation.setAccessControlList(accessControlList); } @Override public final void setOwnership(final Ownership ownership) { - this.ownership = ownership; + this.securityInformation.setOwnership(ownership); } - } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/SecurityInformationDTO.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/SecurityInformationDTO.java new file mode 100644 index 00000000000..cf92f5a0f98 --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/SecurityInformationDTO.java @@ -0,0 +1,40 @@ +package com.sap.sse.security.shared; + +import java.io.Serializable; + +import com.sap.sse.security.shared.AccessControlList; +import com.sap.sse.security.shared.Ownership; +import com.sap.sse.security.shared.SecuredObject; + +/** + * Data transfer object wrapping security information such as {@link AccessControlList access control list} and + * {@link Ownership ownership} of and {@link SecuredObject secured object}. + */ +public class SecurityInformationDTO implements SecuredObject, Serializable { + + private static final long serialVersionUID = -292250850983164293L; + + private AccessControlList accessControlList; + private Ownership ownership; + + @Override + public final AccessControlList getAccessControlList() { + return accessControlList; + } + + @Override + public final Ownership getOwnership() { + return ownership; + } + + @Override + public final void setAccessControlList(final AccessControlList accessControlList) { + this.accessControlList = accessControlList; + } + + @Override + public final void setOwnership(final Ownership ownership) { + this.ownership = ownership; + } + +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SecuredObjectOwnerColumn.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SecuredObjectOwnerColumn.java index a9a53ac3d55..df1693972a8 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SecuredObjectOwnerColumn.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/SecuredObjectOwnerColumn.java @@ -28,7 +28,7 @@ import com.sap.sse.security.ui.client.i18n.StringMessages; */ public class SecuredObjectOwnerColumn extends TextColumn { - private final Function ownerResolver; + private final Function> ownerResolver; /** * Creates a new {@link SecuredObjectOwnerColumn} instance used the provided {@link Function resolver} to determine @@ -39,12 +39,12 @@ public class SecuredObjectOwnerColumn extends TextColum */ public SecuredObjectOwnerColumn(final Function ownerResolver) { final Function ownershipResolver = SecuredObject::getOwnership; - this.ownerResolver = ownershipResolver.andThen(ownerResolver); + this.ownerResolver = ownershipResolver.andThen(ownership -> Optional.ofNullable(ownership).map(ownerResolver)); } @Override public final String getValue(final T object) { - return Optional.ofNullable(ownerResolver.apply(object)).map(Named::getName).orElse(""); + return ownerResolver.apply(object).map(Named::getName).orElse(""); } /** @@ -58,16 +58,14 @@ public class SecuredObjectOwnerColumn extends TextColum * @return {@link SecuredObjectOwnerColumn} instance showing the {@link Ownership#getTenantOwner() tenant owner} */ public static SecuredObjectOwnerColumn getGroupOwnerColumn() { - return new SecuredObjectOwnerColumn<>( - (Ownership t) -> Optional.ofNullable(t).map(x -> x.getTenantOwner()).orElse(null)); + return new SecuredObjectOwnerColumn<>(Ownership::getTenantOwner); } /** * @return {@link SecuredObjectOwnerColumn} instance showing the {@link Ownership#getUserOwner() user owner} */ public static SecuredObjectOwnerColumn getUserOwnerColumn() { - return new SecuredObjectOwnerColumn<>( - (Ownership t) -> Optional.ofNullable(t).map(x -> x.getUserOwner()).orElse(null)); + return new SecuredObjectOwnerColumn<>(Ownership::getUserOwner); } /** diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/RoleDefinitionDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/RoleDefinitionDTO.java index 9e8ec272eda..8c42dc7b162 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/RoleDefinitionDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/RoleDefinitionDTO.java @@ -6,13 +6,13 @@ import com.sap.sse.security.shared.AccessControlList; import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.RoleDefinitionImpl; import com.sap.sse.security.shared.SecuredObject; +import com.sap.sse.security.shared.SecurityInformationDTO; public class RoleDefinitionDTO extends RoleDefinitionImpl implements SecuredObject { - private static final long serialVersionUID = -2580755958300866126L; - - private AccessControlList accessControlList; - private Ownership ownership; + private static final long serialVersionUID = -3340211553071045099L; + + private SecurityInformationDTO securityInformation = new SecurityInformationDTO(); @Deprecated RoleDefinitionDTO() {} // for GWT serialization only @@ -23,22 +23,22 @@ public class RoleDefinitionDTO extends RoleDefinitionImpl implements SecuredObje @Override public final AccessControlList getAccessControlList() { - return accessControlList; + return securityInformation.getAccessControlList(); } @Override public final Ownership getOwnership() { - return ownership; + return securityInformation.getOwnership(); } @Override public final void setAccessControlList(final AccessControlList accessControlList) { - this.accessControlList = accessControlList; + this.securityInformation.setAccessControlList(accessControlList); } @Override public final void setOwnership(final Ownership ownership) { - this.ownership = ownership; + this.securityInformation.setOwnership(ownership); } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index 0184d091c0c..912b8691718 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -9,13 +9,15 @@ import com.sap.sse.security.shared.AccessControlList; import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.Role; import com.sap.sse.security.shared.SecuredObject; +import com.sap.sse.security.shared.SecurityInformationDTO; import com.sap.sse.security.shared.UserGroup; import com.sap.sse.security.shared.WildcardPermission; import com.sap.sse.security.shared.impl.SecurityUserImpl; public class UserDTO extends SecurityUserImpl implements IsSerializable, SecuredObject { - private static final long serialVersionUID = -4807678211983511872L; - + + private static final long serialVersionUID = 7556217539893146187L; + private String email; private String fullName; private String company; @@ -23,8 +25,7 @@ public class UserDTO extends SecurityUserImpl implements IsSerializable, Secured private List accounts; private boolean emailValidated; private List groups; - private AccessControlList accessControlList; - private Ownership ownership; + private SecurityInformationDTO securityInformation = new SecurityInformationDTO(); /** * @param groups may be {@code null} which is equivalent to passing an empty groups collection @@ -99,21 +100,21 @@ public class UserDTO extends SecurityUserImpl implements IsSerializable, Secured @Override public final AccessControlList getAccessControlList() { - return accessControlList; + return securityInformation.getAccessControlList(); } @Override public final Ownership getOwnership() { - return ownership; + return securityInformation.getOwnership(); } @Override public final void setAccessControlList(final AccessControlList accessControlList) { - this.accessControlList = accessControlList; + this.securityInformation.setAccessControlList(accessControlList); } @Override public final void setOwnership(final Ownership ownership) { - this.ownership = ownership; + this.securityInformation.setOwnership(ownership); } } From fd1a6e17d3bec06ad542789ed0e95705df8d0147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 11:00:08 +0100 Subject: [PATCH 03/42] fix for test NPE --- .../src/com/sap/sse/security/test/PermissionCheckerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java index b337363c006..b4c57bd983f 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java @@ -75,8 +75,8 @@ public class PermissionCheckerTest { if (userStore.getUserByName("jonas") != null) { userStore.deleteUser("jonas"); } - user = userStore.createUser("jonas", "jonas@dann.io", userTenant); userTenant = userStore.createUserGroup(userTenantId, "jonas-tenant"); + user = userStore.createUser("jonas", "jonas@dann.io", userTenant); userTenant.add(user); ownership = new OwnershipImpl(user, userTenant); adminTenant.add(adminUser); From 67b16f1705cfb80655854f2fe4b68f522876ccdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 10:53:43 +0100 Subject: [PATCH 04/42] Fixed edit of a user if the current user does not have permission to see already associated roles --- .../src/com/sap/sse/common/Util.java | 6 ++++ .../client/usermanagement/UserEditDialog.java | 31 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 92351091a11..8a3590770e8 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -821,6 +821,12 @@ public class Util { return toStringOrNull.toString(); } + public static boolean equalStringsWithEmptyIsNull(String o1, String o2) { + String effectiveO1 = o1 == null || o1.isEmpty() ? null : o1; + String effectiveO2 = o2 == null || o2.isEmpty() ? null : o2; + return equalsWithNull(effectiveO1, effectiveO2); + } + /** * Pads a numerical value with '0' characters up to a number of digits left and right of the decimal point. If the * number of digits right of the decimal point is zero, no decimal point will be rendered in the result. The sum of diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserEditDialog.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserEditDialog.java index 3629d581fb8..b5d52b73131 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserEditDialog.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/usermanagement/UserEditDialog.java @@ -230,10 +230,35 @@ public class UserEditDialog extends DataEntryDialog{ final Triple roleDefinitionNameAndTenantQualifierNameAndUserQualifierName = RoleImpl .getRoleDefinitionNameAndTenantQualifierNameAndUserQualifierName(roleName); - RoleDefinition roleDefinition = serverRoleDefinitionsByName.get(roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getA()); + + final String roleNameToAdd = roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getA(); + final String roleTenantNameToAdd = roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getB(); + final String roleUsernameToAdd = roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getC(); + + RoleDefinition roleDefinition = null; + for (Role role : userToEdit.getRoles()) { + final String existingRoleName = role.getName(); + final String existingTenantName = role.getQualifiedForTenant() == null ? null : role.getQualifiedForTenant().getName(); + final String existingUsername = role.getQualifiedForUser() == null ? null : role.getQualifiedForUser().getName(); + + // handles unchanged role associations to not rely on the current user to see all roles already associated + if (Util.equalsWithNull(existingRoleName, roleNameToAdd) + && Util.equalStringsWithEmptyIsNull(existingTenantName, roleTenantNameToAdd) + && Util.equalStringsWithEmptyIsNull(existingUsername, roleUsernameToAdd)) { + roleDefinition =role.getRoleDefinition(); + break; + } + } + if (roleDefinition == null) { + roleDefinition = serverRoleDefinitionsByName.get(roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getA()); + } + if (roleDefinition == null) { + // FIXME how to handle this case? Use null as role definition ID and validate afterwards + return null; + } return new Triple<>( - roleDefinition.getId(), /* qualifying tenant name */ roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getB(), - /* qualifying user name */ roleDefinitionNameAndTenantQualifierNameAndUserQualifierName.getC()); + roleDefinition.getId(), /* qualifying tenant name */ roleTenantNameToAdd, + /* qualifying user name */ roleUsernameToAdd); }); } else { roles = Collections.emptyList(); From 835f16939049c22d36a48e25da68aba97d93b4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Fri, 9 Nov 2018 15:49:04 +0100 Subject: [PATCH 05/42] Implemented meta permission checks when granting users additional permissions (not finished yet) --- .../src/com/sap/sse/security/shared/Role.java | 5 + .../com/sap/sse/security/shared/RoleImpl.java | 9 ++ .../ui/client/UserManagementService.java | 2 +- .../ui/server/UserManagementServiceImpl.java | 92 +++++++++++++------ 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java index f5334dfe0a8..db5a09f29d8 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java @@ -44,4 +44,9 @@ public interface Role extends Named { * qualified by a user argument}. */ SecurityUser getQualifiedForUser(); + + /** + * @return the {@link #getQualifiedForTenant()} and {@link #getQualificationAsOwnership()} as {@link Ownership} + */ + Ownership getQualificationAsOwnership(); } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleImpl.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleImpl.java index d5efdea269e..9c6fffa965c 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleImpl.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RoleImpl.java @@ -3,6 +3,7 @@ package com.sap.sse.security.shared; import java.util.Set; import com.sap.sse.common.Util.Triple; +import com.sap.sse.security.shared.impl.OwnershipImpl; /** * For equality and hash code, the {@link RoleDefinition#getId() role definition ID}, the {@link Tenant#getId() tenant ID} of a @@ -117,4 +118,12 @@ public class RoleImpl implements Role { return false; return true; } + + @Override + public Ownership getQualificationAsOwnership() { + if (qualifiedForTenant != null || qualifiedForUser != null) { + return new OwnershipImpl(qualifiedForUser, qualifiedForTenant); + } + return null; + } } 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 0ec719abf29..a9df1340b1e 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 @@ -61,7 +61,7 @@ public interface UserManagementService extends RemoteService { void deleteRoleDefinition(String roleDefinitionIdAsString); - void updateRoleDefinition(RoleDefinitionDTO roleWithNewProperties); + void updateRoleDefinition(RoleDefinitionDTO roleWithNewProperties) throws UnauthorizedException; ArrayList getRoleDefinitions(); 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 82a269f55aa..7b94479c79d 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 @@ -42,6 +42,7 @@ import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.OwnershipAnnotation; import com.sap.sse.security.shared.QualifiedObjectIdentifier; import com.sap.sse.security.shared.Role; +import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.RoleImpl; import com.sap.sse.security.shared.SecurityUser; import com.sap.sse.security.shared.UnauthorizedException; @@ -118,11 +119,24 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U } @Override - public void updateRoleDefinition(RoleDefinitionDTO roleDefinitionWithNewProperties) { - // FIXME an additional check is needed to verify that a user may only grant/revoke permissions that he owns - // In case of role permissions this means the user needs to own an unqualified version of the permission because - // any user may own the role with any qualification - SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.ROLE_DEFINITION.getStringPermissionForObjects(DefaultActions.UPDATE, roleDefinitionWithNewProperties.getId().toString())); + public void updateRoleDefinition(RoleDefinitionDTO roleDefinitionWithNewProperties) throws UnauthorizedException { + SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.ROLE_DEFINITION.getStringPermissionForObjects( + DefaultActions.UPDATE, roleDefinitionWithNewProperties.getId().toString())); + + RoleDefinition existingRole = getSecurityService().getRoleDefinition(roleDefinitionWithNewProperties.getId()); + if (existingRole == null) { + throw new UnauthorizedException("Role does not exist"); + } + Set addedPermissions = new HashSet<>(roleDefinitionWithNewProperties.getPermissions()); + addedPermissions.removeAll(existingRole.getPermissions()); + for (WildcardPermission permissionToAdd : addedPermissions) { + // FIXME check based on actually existing qualifications + if (!getSecurityService().hasCurrentUserMetaPermission(permissionToAdd, null)) { + throw new UnauthorizedException("Not permitted to grant permission " + permissionToAdd + " for role " + + roleDefinitionWithNewProperties.getName()); + } + } + getSecurityService().updateRoleDefinition(roleDefinitionWithNewProperties); } @@ -435,12 +449,14 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U public SuccessInfo setRolesForUser(String username, Iterable> roleDefinitionIdAndTenantQualifierNameAndUsernames) throws UnauthorizedException { - // FIXME an additional check is needed to verify that a user may only grant/revoke permissions that he owns - // The current user needs to have all permissions of a role for the specific tenant qualification or an unqualified version - if (SecurityUtils.getSubject().isPermitted( - SecuredSecurityTypes.USER.getStringPermissionForObjects(UserActions.GRANT_PERMISSION, username)) - && SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.USER - .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username))) { + final boolean isUserPermittedToGrantPermissionsForOtherUser = SecurityUtils.getSubject().isPermitted( + SecuredSecurityTypes.USER.getStringPermissionForObjects(UserActions.GRANT_PERMISSION, username)); + final boolean isUserPermittedToRevokePermissionsForOtherUser = SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.USER + .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username)); + if (!isUserPermittedToGrantPermissionsForOtherUser + && !isUserPermittedToRevokePermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to grant or revoke permissions for user "+username); + } else { User u = getSecurityService().getUserByName(username); if (u == null) { return new SuccessInfo(false, "User does not exist.", /* redirectURL */ null, null); @@ -467,12 +483,26 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U Set roleDefinitionsToRemove = new HashSet<>(); Util.addAll(u.getRoles(), roleDefinitionsToRemove); Util.removeAll(rolesToSet, roleDefinitionsToRemove); - for (Role roleToRemove : roleDefinitionsToRemove) { - getSecurityService().removeRoleFromUser(u, roleToRemove); + if (!roleDefinitionsToRemove.isEmpty() && !isUserPermittedToRevokePermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to revoke permissions for user "+username); } Set rolesToAdd = new HashSet<>(); Util.addAll(rolesToSet, rolesToAdd); Util.removeAll(u.getRoles(), rolesToAdd); + if (!rolesToAdd.isEmpty() && !isUserPermittedToGrantPermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to grant permissions for user "+username); + } + for (Role roleToAdd : rolesToAdd) { + for (WildcardPermission permissionOfRoleToAdd : roleToAdd.getPermissions()) { + if (!getSecurityService().hasCurrentUserMetaPermission(permissionOfRoleToAdd, roleToAdd.getQualificationAsOwnership())) { + throw new UnauthorizedException( + "Not permitted to grant role " + roleToAdd.getName() + " for user " + username); + } + } + } + for (Role roleToRemove : roleDefinitionsToRemove) { + getSecurityService().removeRoleFromUser(u, roleToRemove); + } for (Role roleToAdd : rolesToAdd) { getSecurityService().addRoleForUser(u, roleToAdd); } @@ -481,8 +511,6 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U final UserDTO userDTO = securityDTOFactory.createUserDTOFromUser(u, getSecurityService()); return new SuccessInfo(true, message, /* redirectURL */null, new Pair(userDTO, getAllUser())); - } else { - throw new UnauthorizedException("Not permitted to grant permissions to user"); } } @@ -503,10 +531,14 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public SuccessInfo setPermissionsForUser(String username, Iterable permissions) throws UnauthorizedException { - if (SecurityUtils.getSubject().isPermitted( - SecuredSecurityTypes.USER.getStringPermissionForObjects(UserActions.GRANT_PERMISSION, username)) - && SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.USER - .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username))) { + final boolean isUserPermittedToGrantPermissionsForOtherUser = SecurityUtils.getSubject().isPermitted( + SecuredSecurityTypes.USER.getStringPermissionForObjects(UserActions.GRANT_PERMISSION, username)); + final boolean isUserPermittedToRevokePermissionsForOtherUser = SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.USER + .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username)); + if (!isUserPermittedToGrantPermissionsForOtherUser + && !isUserPermittedToRevokePermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to grant or revoke permissions for user "+username); + } else { User u = getSecurityService().getUserByName(username); if (u == null) { return new SuccessInfo(false, "User does not exist.", /* redirectURL */null, null); @@ -514,26 +546,30 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U Set permissionsToRemove = new HashSet<>(); Util.addAll(u.getPermissions(), permissionsToRemove); Util.removeAll(permissions, permissionsToRemove); - for (WildcardPermission permissionToRemove : permissionsToRemove) { - getSecurityService().removePermissionFromUser(username, permissionToRemove); + if (!permissionsToRemove.isEmpty() && !isUserPermittedToRevokePermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to revoke permissions for user "+username); } Set permissionsToAdd = new HashSet<>(); Util.addAll(permissions, permissionsToAdd); Util.removeAll(u.getPermissions(), permissionsToAdd); + if (!permissionsToAdd.isEmpty() && !isUserPermittedToGrantPermissionsForOtherUser) { + throw new UnauthorizedException("Not permitted to grant permissions for user "+username); + } for (WildcardPermission permissionToAdd : permissionsToAdd) { - OwnershipAnnotation currentUsersOwnerShip = getSecurityService() - .getOwnership(getSecurityService().getCurrentUser().getIdentifier()); - if (getSecurityService().hasCurrentUserMetaPermission(permissionToAdd, - currentUsersOwnerShip.getAnnotation())) { - getSecurityService().addPermissionForUser(username, permissionToAdd); + if (!getSecurityService().hasCurrentUserMetaPermission(permissionToAdd, null)) { + throw new UnauthorizedException("Not permitted to grant permission "+permissionToAdd+" for user "+username); } } + for (WildcardPermission permissionToRemove : permissionsToRemove) { + getSecurityService().removePermissionFromUser(username, permissionToRemove); + } + for (WildcardPermission permissionToAdd : permissionsToAdd) { + getSecurityService().addPermissionForUser(username, permissionToAdd); + } final String message = "Set roles " + permissions + " for user " + username; final UserDTO userDTO = securityDTOFactory.createUserDTOFromUser(u, getSecurityService()); return new SuccessInfo(true, message, /* redirectURL */null, new Pair(userDTO, getAllUser())); - } else { - throw new UnauthorizedException("Not permitted to grant or revoke permissions for user "+username); } } From 0973c1ba73221a83fad02edf3cb6748632e1d756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 11:11:44 +0100 Subject: [PATCH 06/42] Improved error handling to show better error messages to the user --- .../ui/server/UserManagementServiceImpl.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) 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 7b94479c79d..85095c33c32 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 @@ -455,7 +455,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username)); if (!isUserPermittedToGrantPermissionsForOtherUser && !isUserPermittedToRevokePermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to grant or revoke permissions for user "+username); + return new SuccessInfo(false, "Not permitted to grant or revoke permissions for user " + username, + /* redirectURL */null, null); } else { User u = getSecurityService().getUserByName(username); if (u == null) { @@ -484,19 +485,22 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U Util.addAll(u.getRoles(), roleDefinitionsToRemove); Util.removeAll(rolesToSet, roleDefinitionsToRemove); if (!roleDefinitionsToRemove.isEmpty() && !isUserPermittedToRevokePermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to revoke permissions for user "+username); + return new SuccessInfo(false, "Not permitted to revoke permissions for user " + username, + /* redirectURL */null, null); } Set rolesToAdd = new HashSet<>(); Util.addAll(rolesToSet, rolesToAdd); Util.removeAll(u.getRoles(), rolesToAdd); if (!rolesToAdd.isEmpty() && !isUserPermittedToGrantPermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to grant permissions for user "+username); + return new SuccessInfo(false, "Not permitted to grant permissions for user " + username, + /* redirectURL */null, null); } for (Role roleToAdd : rolesToAdd) { for (WildcardPermission permissionOfRoleToAdd : roleToAdd.getPermissions()) { if (!getSecurityService().hasCurrentUserMetaPermission(permissionOfRoleToAdd, roleToAdd.getQualificationAsOwnership())) { - throw new UnauthorizedException( - "Not permitted to grant role " + roleToAdd.getName() + " for user " + username); + return new SuccessInfo(false, + "Not permitted to grant role " + roleToAdd.getName() + " for user " + username, + /* redirectURL */null, null); } } } @@ -537,7 +541,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U .getStringPermissionForObjects(UserActions.REVOKE_PERMISSION, username)); if (!isUserPermittedToGrantPermissionsForOtherUser && !isUserPermittedToRevokePermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to grant or revoke permissions for user "+username); + return new SuccessInfo(false, "Not permitted to grant or revoke permissions for user " + username, + /* redirectURL */null, null); } else { User u = getSecurityService().getUserByName(username); if (u == null) { @@ -547,17 +552,21 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U Util.addAll(u.getPermissions(), permissionsToRemove); Util.removeAll(permissions, permissionsToRemove); if (!permissionsToRemove.isEmpty() && !isUserPermittedToRevokePermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to revoke permissions for user "+username); + return new SuccessInfo(false, "Not permitted to revoke permissions for user " + username, + /* redirectURL */null, null); } Set permissionsToAdd = new HashSet<>(); Util.addAll(permissions, permissionsToAdd); Util.removeAll(u.getPermissions(), permissionsToAdd); if (!permissionsToAdd.isEmpty() && !isUserPermittedToGrantPermissionsForOtherUser) { - throw new UnauthorizedException("Not permitted to grant permissions for user "+username); + return new SuccessInfo(false, "Not permitted to grant permissions for user " + username, + /* redirectURL */null, null); } for (WildcardPermission permissionToAdd : permissionsToAdd) { if (!getSecurityService().hasCurrentUserMetaPermission(permissionToAdd, null)) { - throw new UnauthorizedException("Not permitted to grant permission "+permissionToAdd+" for user "+username); + return new SuccessInfo(false, + "Not permitted to grant permission " + permissionToAdd + " for user " + username, + /* redirectURL */null, null); } } for (WildcardPermission permissionToRemove : permissionsToRemove) { From eaa13176fea91b957810c061c57f8f87eaa519da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 11:21:34 +0100 Subject: [PATCH 07/42] fixed UserStoreWithPersistenceTest for defaultTenant per server logic --- .../com/sap/sse/security/test/UserStoreWithPersistenceTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java index c4203ac52ae..b7508773548 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java @@ -177,6 +177,8 @@ public class UserStoreWithPersistenceTest { final User user = store.createUser(username, email, defaultTenant); defaultTenant.add(user); store.updateUserGroup(defaultTenant); + user.getDefaultTenantMap().put(serverName, defaultTenant); + store.updateUser(user); assertSame(defaultTenant, user.getDefaultTenant(serverName)); assertEquals(1, Util.size(defaultTenant.getUsers())); assertSame(user, defaultTenant.getUsers().iterator().next()); From 9f8aafd5a65354e6e54c2465418a8e8ee824b282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 14:39:12 +0100 Subject: [PATCH 08/42] An empty part is equivalent to the wildcard token --- .../src/com/sap/sse/security/shared/PermissionChecker.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionChecker.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionChecker.java index ec031280a6f..a157d29049a 100644 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionChecker.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionChecker.java @@ -198,7 +198,7 @@ public class PermissionChecker { final boolean isTypePartWildcard; if (parts.size() >= 1) { typeParts = parts.get(0); - isTypePartWildcard = typeParts.contains(WildcardPermission.WILDCARD_TOKEN); + isTypePartWildcard = typeParts.isEmpty() || typeParts.contains(WildcardPermission.WILDCARD_TOKEN); } else { typeParts = null; isTypePartWildcard = true; @@ -208,7 +208,7 @@ public class PermissionChecker { final boolean isActionPartWildcard; if (parts.size() >= 2) { actionParts = parts.get(1); - isActionPartWildcard = actionParts.contains(WildcardPermission.WILDCARD_TOKEN); + isActionPartWildcard = actionParts.isEmpty() || actionParts.contains(WildcardPermission.WILDCARD_TOKEN); } else { actionParts = null; isActionPartWildcard = true; @@ -218,7 +218,7 @@ public class PermissionChecker { final boolean isIdPartWildcard; if (parts.size() >= 3) { idParts = parts.get(2); - isIdPartWildcard = idParts.contains(WildcardPermission.WILDCARD_TOKEN); + isIdPartWildcard = idParts.isEmpty() || idParts.contains(WildcardPermission.WILDCARD_TOKEN); } else { idParts = null; isIdPartWildcard = true; From ab86ca0d01a8feceeddd664dfdfbb19e48570448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 14:41:13 +0100 Subject: [PATCH 09/42] Added a builder to not need to construct complex permissions (e.g. multiple types, or wildcard type) as String --- .../security/shared/WildcardPermission.java | 73 +++++++++++++++++-- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java index 109310ecfc6..e2be1ed48a0 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java @@ -21,10 +21,12 @@ package com.sap.sse.security.shared; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import com.sap.sse.security.shared.HasPermissions.Action; import com.sap.sse.security.shared.impl.QualifiedObjectIdentifierImpl; import com.sap.sse.security.shared.impl.WildcardPermissionEncoder; @@ -126,26 +128,31 @@ public class WildcardPermission implements Serializable { WildcardPermission() {} public WildcardPermission(String wildcardString) { - setParts(wildcardString); + this(createPartsFromString(wildcardString)); } - - protected void setParts(String wildcardString) { + + private WildcardPermission(List> parts) { + this.parts = parts; + } + + protected static List> createPartsFromString(String wildcardString) { if (wildcardString == null || wildcardString.trim().length() == 0) { throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted."); } wildcardString = wildcardString.trim(); - List parts = Arrays.asList(wildcardString.split(PART_DIVIDER_TOKEN)); - this.parts = new ArrayList>(); - for (String part : parts) { + List rawParts = Arrays.asList(wildcardString.split(PART_DIVIDER_TOKEN)); + List> parts = new ArrayList<>(); + for (String part : rawParts) { Set subparts = new HashSet<>(Arrays.asList(part.split(SUBPART_DIVIDER_TOKEN))); if (subparts.isEmpty()) { throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted."); } - this.parts.add(subparts); + parts.add(subparts); } - if (this.parts.isEmpty()) { + if (parts.isEmpty()) { throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted."); } + return parts; } /*-------------------------------------------- @@ -251,5 +258,55 @@ public class WildcardPermission implements Serializable { } return result; } + + public static WildcardPermissionBuilder builder() { + return new WildcardPermissionBuilder(); + } + /** + * Builder to create non-primitive {@link WildcardPermission} instances. E.g. it is possible to use multiple types + * or wildcard for the type part. + */ + public static class WildcardPermissionBuilder { + private Set types = new HashSet<>(); + private Set actions = new HashSet<>(); + private Set ids = new HashSet<>(); + + public WildcardPermissionBuilder withTypes(HasPermissions... types) { + for (HasPermissions hasPermissions : types) { + this.types.add(hasPermissions.getName()); + } + return this; + } + + public WildcardPermissionBuilder withActions(Action... actions) { + for (Action action : actions) { + this.actions.add(action.name()); + } + return this; + } + + public WildcardPermissionBuilder withIds(String... ids) { + this.ids.addAll(Arrays.asList(ids)); + return this; + } + + public WildcardPermission build() { + final List> parts = new ArrayList<>(2); + if (types.isEmpty()) { + parts.add(new HashSet<>(Collections.singleton(WILDCARD_TOKEN))); + } else { + parts.add(types); + } + if (!actions.isEmpty()) { + parts.add(actions); + } else if (!ids.isEmpty()) { + parts.add(new HashSet<>(Collections.singleton(WILDCARD_TOKEN))); + } + if (!ids.isEmpty()) { + parts.add(ids); + } + return new WildcardPermission(parts); + } + } } From ccbd65b0a522b9071246d7d12e9501fa509075ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 14:43:05 +0100 Subject: [PATCH 10/42] Using WildcardPermissionBuilder to construct complex permissions instead of hard-coding permission strings --- .../com/sap/sse/security/shared/UserRole.java | 8 ++--- .../security/test/PermissionCheckerTest.java | 33 ++++++++++--------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/UserRole.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/UserRole.java index 98d57340240..9802e250aef 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/UserRole.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/UserRole.java @@ -10,12 +10,8 @@ public class UserRole extends RolePrototype { UserRole() { super("user", "ad1d5148-b13d-4464-90c4-7c396e4d4e2e", - new WildcardPermission(SecuredSecurityTypes.USER.getStringPermission(DefaultActions.UPDATE)), - new WildcardPermission(SecuredSecurityTypes.USER.getStringPermission(DefaultActions.READ)), - new WildcardPermission("*:" + DefaultActions.CREATE.name() + ":*"), - new WildcardPermission("*:" + DefaultActions.READ.name() + ":*"), - new WildcardPermission("*:" + DefaultActions.UPDATE.name() + ":*"), - new WildcardPermission("*:" + DefaultActions.DELETE.name() + ":*")); + new WildcardPermission(SecuredSecurityTypes.USER.getStringPermission(DefaultActions.READ, DefaultActions.UPDATE)), + WildcardPermission.builder().withActions(DefaultActions.READ_AND_WRITE_ACTIONS).build()); } public static UserRole getInstance() { diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java index b4c57bd983f..7c0887ae886 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/PermissionCheckerTest.java @@ -210,7 +210,7 @@ public class PermissionCheckerTest { @Test public void testMetaPermissionCheck() { - final WildcardPermission allPermission = new WildcardPermission("*"); + final WildcardPermission allPermission = WildcardPermission.builder().build(); final WildcardPermission singleTypePermission = type1.getPermission(); assertFalse(checkMetaPermissionWithGrantedUserPermissions(singleTypePermission)); assertTrue(checkMetaPermissionWithGrantedUserPermissions(singleTypePermission, allPermission)); @@ -221,8 +221,7 @@ public class PermissionCheckerTest { assertTrue(checkMetaPermissionWithGrantedUserPermissions(singleTypePermission, type1.getPermission(DefaultActions.READ, DefaultActions.UPDATE))); - final WildcardPermission combinedTypePermission = new WildcardPermission( - type1.getName() + "," + type2.getName()); + final WildcardPermission combinedTypePermission = WildcardPermission.builder().withTypes(type1, type2).build(); assertFalse(checkMetaPermissionWithGrantedUserPermissions(combinedTypePermission)); assertFalse(checkMetaPermissionWithGrantedUserPermissions(combinedTypePermission, type1.getPermission())); assertTrue(checkMetaPermissionWithGrantedUserPermissions(combinedTypePermission, allPermission)); @@ -234,8 +233,8 @@ public class PermissionCheckerTest { assertTrue(checkMetaPermissionWithGrantedUserPermissions(combinedTypePermission, type1.getPermission(), type2.getPermission(DefaultActions.READ, DefaultActions.DELETE))); - final WildcardPermission combinedTypeWithDistinctActionPermission = new WildcardPermission( - type1.getName() + "," + type2.getName() + ":" + DefaultActions.READ); + final WildcardPermission combinedTypeWithDistinctActionPermission = WildcardPermission.builder() + .withTypes(type1, type2).withActions(DefaultActions.READ).build(); assertFalse(checkMetaPermissionWithGrantedUserPermissions(combinedTypeWithDistinctActionPermission)); assertFalse(checkMetaPermissionWithGrantedUserPermissions(combinedTypeWithDistinctActionPermission, type1.getPermission())); @@ -265,7 +264,7 @@ public class PermissionCheckerTest { @Test public void testAnyPermissionCheck() { - final WildcardPermission allPermission = new WildcardPermission("*"); + final WildcardPermission allPermission = WildcardPermission.builder().build(); final WildcardPermission singleTypePermission = type1.getPermission(); assertFalse(checkAnyPermissionWithGrantedUserPermissions(singleTypePermission)); assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermission, allPermission)); @@ -276,9 +275,10 @@ public class PermissionCheckerTest { assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermission, type1.getPermission(DefaultActions.READ, DefaultActions.UPDATE))); assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermission, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE).build())); assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermission, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE + ":*"))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE) + .withIds(WildcardPermission.WILDCARD_TOKEN).build())); final WildcardPermission singleTypePermissionWithAction = type1.getPermission(DefaultActions.READ); assertFalse(checkAnyPermissionWithGrantedUserPermissions(singleTypePermissionWithAction)); @@ -290,12 +290,12 @@ public class PermissionCheckerTest { assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermissionWithAction, type1.getPermission(DefaultActions.READ, DefaultActions.UPDATE))); assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermissionWithAction, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE).build())); assertTrue(checkAnyPermissionWithGrantedUserPermissions(singleTypePermissionWithAction, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE + ":*"))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE) + .withIds(WildcardPermission.WILDCARD_TOKEN).build())); - final WildcardPermission combinedTypePermission = new WildcardPermission( - type1.getName() + "," + type2.getName()); + final WildcardPermission combinedTypePermission = WildcardPermission.builder().withTypes(type1, type2).build(); assertFalse(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission)); assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission, type1.getPermission())); assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission, allPermission)); @@ -309,12 +309,13 @@ public class PermissionCheckerTest { assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission, type1.getPermission(DefaultActions.READ, DefaultActions.UPDATE))); assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE).build())); assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypePermission, - new WildcardPermission("*:" + DefaultActions.READ + "," + DefaultActions.UPDATE + ":*"))); + WildcardPermission.builder().withActions(DefaultActions.READ, DefaultActions.UPDATE) + .withIds(WildcardPermission.WILDCARD_TOKEN).build())); - final WildcardPermission combinedTypeWithDistinctActionPermission = new WildcardPermission( - type1.getName() + "," + type2.getName() + ":" + DefaultActions.READ); + final WildcardPermission combinedTypeWithDistinctActionPermission = WildcardPermission.builder() + .withTypes(type1, type2).withActions(DefaultActions.READ).build(); assertFalse(checkAnyPermissionWithGrantedUserPermissions(combinedTypeWithDistinctActionPermission)); assertTrue(checkAnyPermissionWithGrantedUserPermissions(combinedTypeWithDistinctActionPermission, type1.getPermission())); From af9315f6545fe730cfe91412417ed7cffaea712d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 14:48:39 +0100 Subject: [PATCH 11/42] Added the possibility to let sse.security automatically create RoleDefinitions from RolePrototypes provided by domain server bundles --- .../sse/security/RolePrototypeProvider.java | 13 +++++++ .../com/sap/sse/security/impl/Activator.java | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 java/com.sap.sse.security/src/com/sap/sse/security/RolePrototypeProvider.java diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/RolePrototypeProvider.java b/java/com.sap.sse.security/src/com/sap/sse/security/RolePrototypeProvider.java new file mode 100644 index 00000000000..469f9b565ba --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/RolePrototypeProvider.java @@ -0,0 +1,13 @@ +package com.sap.sse.security; + +import com.sap.sse.security.shared.RolePrototype; + +/** + * Implementations provide {@link RolePrototype} instances to be automatically created in the system if a role with the + * specific ID does not already exist. Implementations need to be registered as OSGi service. + */ +public interface RolePrototypeProvider { + + RolePrototype getRolePrototype(); + +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java index 5d96974dcd7..ae7e096b09b 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java @@ -7,16 +7,21 @@ import java.util.logging.Logger; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import com.sap.sse.mail.MailService; import com.sap.sse.replication.Replicable; import com.sap.sse.security.AccessControlStore; +import com.sap.sse.security.RolePrototypeProvider; import com.sap.sse.security.SecurityService; import com.sap.sse.security.UserStore; import com.sap.sse.security.UsernamePasswordRealm; import com.sap.sse.security.shared.HasPermissionsProvider; +import com.sap.sse.security.shared.RoleDefinition; +import com.sap.sse.security.shared.RolePrototype; import com.sap.sse.util.ClearStateTestSupport; import com.sap.sse.util.ServiceTrackerFactory; @@ -37,6 +42,8 @@ public class Activator implements BundleActivator { */ private static UserStore testUserStore; private static AccessControlStore testAccessControlStore; + + private ServiceTracker rolePrototypeProviderTracker; public static void setTestStores(UserStore theTestUserStore, AccessControlStore theTestAccessControlStore) { testUserStore = theTestUserStore; @@ -84,6 +91,33 @@ public class Activator implements BundleActivator { context.registerService(ClearStateTestSupport.class.getName(), securityService, null); Logger.getLogger(Activator.class.getName()).info("Security Service registered."); } + + private void createRoleDefinitionsFromPrototypes(BundleContext bundleContext, UserStore userStore) { + rolePrototypeProviderTracker = new ServiceTracker<>( + bundleContext, RolePrototypeProvider.class, /* customizer */ new ServiceTrackerCustomizer() { + @Override + public RolePrototypeProvider addingService(ServiceReference reference) { + final RolePrototypeProvider service = context.getService(reference); + final RolePrototype rolePrototype = service.getRolePrototype(); + final RoleDefinition potentiallyExistingRoleDefinition = userStore.getRoleDefinition(rolePrototype.getId()); + if (potentiallyExistingRoleDefinition == null) { + userStore.createRoleDefinition(rolePrototype.getId(), rolePrototype.getName(), rolePrototype.getPermissions()); + } + return service; + } + + @Override + public void modifiedService(ServiceReference reference, + RolePrototypeProvider service) { + } + + @Override + public void removedService(ServiceReference reference, + RolePrototypeProvider service) { + } + }); + rolePrototypeProviderTracker.open(); + } private void waitForUserStoreService(BundleContext bundleContext) { context = bundleContext; @@ -100,6 +134,7 @@ public class Activator implements BundleActivator { final UserStore userStore = tracker.waitForService(0); final AccessControlStore accessControlStore = accessControlStoreTracker.waitForService(0); logger.info("Obtained UserStore service "+userStore); + createRoleDefinitionsFromPrototypes(bundleContext, userStore); createAndRegisterSecurityService(bundleContext, userStore, accessControlStore); } catch (InterruptedException e) { logger.log(Level.SEVERE, "Interrupted while waiting for UserStore service", e); @@ -114,6 +149,10 @@ public class Activator implements BundleActivator { * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */ public void stop(BundleContext bundleContext) throws Exception { + if (rolePrototypeProviderTracker != null) { + rolePrototypeProviderTracker.close(); + rolePrototypeProviderTracker = null; + } if (registration != null) { registration.unregister(); } From c945c9013cb38bac322d813b51f4bbf53beb3d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 14:50:50 +0100 Subject: [PATCH 12/42] Added sailing viewer role --- .../sap/sailing/server/impl/Activator.java | 6 ++++- .../server/impl/SailingViewerRole.java | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index 1fa80287b60..abc7a924181 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -54,6 +54,7 @@ import com.sap.sse.mail.queue.impl.ExecutorMailQueue; import com.sap.sse.osgi.CachedOsgiTypeBasedServiceFinderFactory; import com.sap.sse.replication.Replicable; import com.sap.sse.security.PreferenceConverter; +import com.sap.sse.security.RolePrototypeProvider; import com.sap.sse.security.SecurityService; import com.sap.sse.security.shared.HasPermissionsProvider; import com.sap.sse.util.ClearStateTestSupport; @@ -120,8 +121,11 @@ public class Activator implements BundleActivator { trackedRegattaListener = new OSGiBasedTrackedRegattaListener(context); - registrations.add(context.registerService(HasPermissionsProvider.class.getName(), + registrations.add(context.registerService(HasPermissionsProvider.class, (HasPermissionsProvider) SecuredDomainType::getAllInstances, null)); + + registrations.add(context.registerService(RolePrototypeProvider.class, + (RolePrototypeProvider) SailingViewerRole::getInstance, null)); final TrackedRaceStatisticsCache trackedRaceStatisticsCache = new TrackedRaceStatisticsCacheImpl(); registrations.add(context.registerService(TrackedRaceStatisticsCache.class.getName(), trackedRaceStatisticsCache, null)); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java new file mode 100644 index 00000000000..32ca226c59c --- /dev/null +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java @@ -0,0 +1,27 @@ +package com.sap.sailing.server.impl; + +import com.sap.sailing.domain.common.security.SecuredDomainType; +import com.sap.sse.security.shared.HasPermissions.DefaultActions; +import com.sap.sse.security.shared.RolePrototype; +import com.sap.sse.security.shared.WildcardPermission; + +/** + * Specifies a role that when associated to a user gives read access to the sailing domain parts needed for the home + * page and basic analytical frontends. + */ +public class SailingViewerRole extends RolePrototype { + private static final long serialVersionUID = 3291793984984443193L; + + private static final SailingViewerRole INSTANCE = new SailingViewerRole(); + + SailingViewerRole() { + super("sailing_viewer", "c42948df-517b-45cb-9fa9-d1e79f18e115", + WildcardPermission.builder().withTypes(SecuredDomainType.EVENT, SecuredDomainType.LEADERBOARD_GROUP, + SecuredDomainType.LEADERBOARD, SecuredDomainType.REGATTA, SecuredDomainType.TRACKED_RACE) + .withActions(DefaultActions.READ).build()); + } + + public static SailingViewerRole getInstance() { + return INSTANCE; + } +} From e9be7109b145427beb02b28aa3c2fb1a4c1efedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 15:07:33 +0100 Subject: [PATCH 13/42] initial parts of server configuration ui --- .../LocalServerManagementPanel.java | 21 +++++++++++++-- .../sailing/gwt/ui/client/StringMessages.java | 2 ++ .../gwt/ui/client/StringMessages.properties | 3 ++- .../gwt/ui/server/SailingServiceImpl.java | 27 +++++++++++++++++-- .../gwt/ui/shared/ServerConfigurationDTO.java | 14 +++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java index fcbc8c2606e..d7241931134 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java @@ -25,6 +25,8 @@ public class LocalServerManagementPanel extends SimplePanel { private CheckBox isStandaloneServerCheckbox; private Label serverNameLabel; private Label buildVersionLabel; + private CheckBox isPublicServer; + private CheckBox isSelfServiceServer; public LocalServerManagementPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { @@ -74,17 +76,30 @@ public class LocalServerManagementPanel extends SimplePanel { serverConfigurationChanged(); } }); + + isPublicServer = new CheckBox(); + isPublicServer.setEnabled(false); + isSelfServiceServer = new CheckBox(); + isSelfServiceServer.setEnabled(false); - Grid grid = new Grid(2, 2); + Grid grid = new Grid(3, 2); grid.setWidget(0, 0, new Label(stringMessages.standaloneServer() + ":")); grid.setWidget(0, 1, isStandaloneServerCheckbox); + + grid.setWidget(1, 0, new Label(stringMessages.isPublic() + ":")); + grid.setWidget(1, 1, isPublicServer); + + grid.setWidget(2, 0, new Label(stringMessages.selfServiceServer() + ":")); + grid.setWidget(2, 1, isSelfServiceServer); + serverConfigurationContentPanel.add(grid); refreshServerConfiguration(); } private void serverConfigurationChanged() { - ServerConfigurationDTO serverConfig = new ServerConfigurationDTO(isStandaloneServerCheckbox.getValue()); + ServerConfigurationDTO serverConfig = new ServerConfigurationDTO(isStandaloneServerCheckbox.getValue(), + isPublicServer.getValue(), isSelfServiceServer.getValue()); sailingService.updateServerConfiguration(serverConfig, new AsyncCallback() { @Override @@ -134,5 +149,7 @@ public class LocalServerManagementPanel extends SimplePanel { private void updateServerConfiguration(ServerConfigurationDTO result) { isStandaloneServerCheckbox.setValue(result.isStandaloneServer(), true); + isPublicServer.setValue(result.isPublic(), true); + isSelfServiceServer.setValue(result.isSelfService(), true); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 43ee8cd03c8..437bdcb5516 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -2199,4 +2199,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String showInDataMining(); String confirmDeletion(); + + String selfServiceServer(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index dd870cf2b30..5c6781bec7d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -2193,4 +2193,5 @@ pleaseSelectCompetitorFirst=Please select a competitor first. sailorProfileRemoveMessage=Are you sure you want to remove this Sailor Profile? moreLoginInformationSectionSailorProfilesDescription=After logging in, you can now find a new area under your user profile, which is called 'Sailor Profiles'. You can add some competitors to such a Sailor Profile and get more insights about these competitors. In a Sailor Profile, you can see all the events your favorite competitors participated in and compare their placements to each others. Additionally, you get an overview about the most important statistics, like the maximum speed or the average distance to the start line with direct links to the Race Map to see the exact point in time, where for example the fastest competitor reached his record. For aggregative statistics like the average distance to startline at racestart, you can click on the arrow and are forwarded to Data Mining where you can drill down on the average values. moreLoginInformationSectionSailorProfilesHeading=Sailor Profiles -confirmDeletion=Confirm Deletion \ No newline at end of file +confirmDeletion=Confirm Deletion +selfServiceServer=Server has Self-Service \ No newline at end of file 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 06a7535f924..106a8138598 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 @@ -590,6 +590,7 @@ import com.sap.sse.security.ActionWithResult; import com.sap.sse.security.SecurityService; import com.sap.sse.security.SessionUtils; import com.sap.sse.security.shared.HasPermissions.DefaultActions; +import com.sap.sse.security.shared.User; import com.sap.sse.security.shared.UserGroup; import com.sap.sse.security.shared.impl.WildcardPermissionEncoder; import com.sap.sse.security.ui.server.SecurityDTOUtil; @@ -4689,7 +4690,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public ServerConfigurationDTO getServerConfiguration() { SailingServerConfiguration sailingServerConfiguration = getService().getSailingServerConfiguration(); - ServerConfigurationDTO result = new ServerConfigurationDTO(sailingServerConfiguration.isStandaloneServer()); + ServerConfigurationDTO result = new ServerConfigurationDTO(sailingServerConfiguration.isStandaloneServer(), + isPublicServer(), isSelfServiceServer()); return result; } @@ -8425,7 +8427,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void setDefaultTenantForCurrentServer(String tennant) { - // TODO Auto-generated method stub + User user = getSecurityService().getCurrentUser(); + if (user != null) { + if (tennant == null) { + user.getDefaultTenantMap().remove(ServerInfo.getName()); + } else { + UserGroup userGroupCandidate = getSecurityService().getUserGroupByName(tennant); + // FIXME allow admins to pose as any tenant? + if (Util.contains(user.getUserGroups(), userGroupCandidate)) { + user.getDefaultTenantMap().put(ServerInfo.getName(), userGroupCandidate); + } + } + } } @Override @@ -8437,4 +8450,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return tenants; } + + private boolean isSelfServiceServer() { + // TODO Auto-generated method stub + return false; + } + + private boolean isPublicServer() { + // TODO Auto-generated method stub + return false; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java index 7fb821d28ab..33808e852d3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java @@ -4,16 +4,28 @@ import com.google.gwt.user.client.rpc.IsSerializable; public class ServerConfigurationDTO implements IsSerializable { private boolean isStandaloneServer; + private boolean isPublic; + private boolean isSelfService; // for GWT ServerConfigurationDTO() { } - public ServerConfigurationDTO(boolean isStandaloneServer) { + public ServerConfigurationDTO(boolean isStandaloneServer, boolean isPublic, boolean isSelfService) { this.isStandaloneServer = isStandaloneServer; + this.isPublic = isPublic; + this.isSelfService = isSelfService; } public boolean isStandaloneServer() { return isStandaloneServer; } + + public boolean isPublic() { + return isPublic; + } + + public boolean isSelfService() { + return isSelfService; + } } From 0d4765dda4d87528042928b2d5db6697ed01f76e Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 12 Nov 2018 15:09:03 +0100 Subject: [PATCH 14/42] Added a method (+tests) in UserStore to find out if other users have a certain role and return them. --- .../test/UserStoreWithPersistenceTest.java | 120 +++++++++++++++++- .../userstore/mongodb/UserStoreImpl.java | 28 ++++ .../src/com/sap/sse/security/UserStore.java | 10 ++ 3 files changed, 152 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java index b7508773548..32babbe941a 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java @@ -1,16 +1,21 @@ package com.sap.sse.security.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; @@ -18,10 +23,16 @@ import org.junit.Test; import com.mongodb.DB; import com.mongodb.MongoException; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.mongodb.MongoDBConfiguration; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.security.UserImpl; import com.sap.sse.security.UserStore; +import com.sap.sse.security.shared.Ownership; +import com.sap.sse.security.shared.Role; +import com.sap.sse.security.shared.RoleDefinitionImpl; +import com.sap.sse.security.shared.RoleImpl; +import com.sap.sse.security.shared.SecurityUser; import com.sap.sse.security.shared.User; import com.sap.sse.security.shared.UserGroup; import com.sap.sse.security.shared.UserGroupManagementException; @@ -40,7 +51,7 @@ public class UserStoreWithPersistenceTest { private final String serverName = "dummyServer"; private final String prefKey = "pk"; private final String prefValue = "pv"; - + private final UUID userGroupId = UUID.randomUUID(); private final String userGroupName = "usergroup"; private UserGroup defaultTenant; @@ -81,7 +92,7 @@ public class UserStoreWithPersistenceTest { assertNotNull(store.getUserByName(username)); assertNotNull(store.getUserByEmail(email)); } - + @Test public void testMasterdataIsSaved() throws UserManagementException { store.createUser(username, email, defaultTenant); @@ -142,7 +153,7 @@ public class UserStoreWithPersistenceTest { newStore(); assertNull(store.getPreference(username, prefKey)); } - + @Test public void testCreateUserGroup() throws UserGroupManagementException, UserManagementException { store.deleteUserGroup(defaultTenant); @@ -157,9 +168,9 @@ public class UserStoreWithPersistenceTest { assertNotNull(store.getUserGroup(userGroupId)); assertNotNull(store.getUserGroupByName(userGroupName)); final User loadedUser = store.getUserByName(username); - assertTrue(Util.contains(Util.map(loadedUser.getUserGroups(), g->g.getName()), userGroupName)); + assertTrue(Util.contains(Util.map(loadedUser.getUserGroups(), g -> g.getName()), userGroupName)); } - + @Test public void testDeleteUserGroup() throws UserGroupManagementException { UserGroup userGroup = store.createUserGroup(userGroupId, userGroupName); @@ -171,7 +182,7 @@ public class UserStoreWithPersistenceTest { assertNull(store.getUserGroup(userGroupId)); assertNull(store.getUserGroupByName(userGroupName)); } - + @Test public void testTenantUsers() throws UserManagementException, UserGroupManagementException { final User user = store.createUser(username, email, defaultTenant); @@ -216,4 +227,101 @@ public class UserStoreWithPersistenceTest { assertEquals(1, Util.size(store.getUserGroupsOfUser(loadedUser))); assertSame(loadedGroup, store.getUserGroupsOfUser(loadedUser).iterator().next()); } + + @Test + public void testGetOtherUsersHaveRole() throws UserManagementException, UserGroupManagementException { + User user = store.createUser("def", "d@test.de", store.createUserGroup(UUID.randomUUID(), "def-tentant")); + RoleDefinitionImpl roleDefinition = new RoleDefinitionImpl(UUID.randomUUID(), "My-Test-Role"); + store.createRoleDefinition(roleDefinition.getId(), roleDefinition.getName(), new ArrayList<>()); + UserGroup userGroup = store.createUserGroup(UUID.randomUUID(), "Test-Usergroup"); + + // tenant is null + testWithTenantNull(user, roleDefinition, userGroup); + + // user is null + testWithUserNull(user, roleDefinition, userGroup); + + // tenant and user are both given (both not null) + testWithTenantAndUserNotNull(user, roleDefinition, userGroup); + + // neither tenant nor user are given (both null) + testWithTenantAndUserNull(user, roleDefinition); + } + + /** Test getOtherUsersHaveRole with both tenant and user null. */ + private void testWithTenantAndUserNull(User user, RoleDefinitionImpl roleDefinition) + throws UserManagementException { + Role role = new RoleImpl(roleDefinition, null, null); + store.addRoleForUser(user.getName(), role); + + Iterable rolesFromUser = store.getRolesFromUser(user.getName()); + assertFalse(Util.size(rolesFromUser) == 0); + assertTrue(Util.contains(rolesFromUser, role)); + Pair> result = store.getOtherUsersHaveRole(role); + assertTrue(result.getA()); + assertNull(result.getB()); + + store.removeRoleFromUser(user.getName(), role); + } + + /** Test checkIfOtherUsersHaveRole with both tenant and user not null. */ + private void testWithTenantAndUserNotNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) + throws UserManagementException { + Role role = new RoleImpl(roleDefinition, userGroup, user); + store.addRoleForUser(user.getName(), role); + assertThatNoUserHasWildcardRole(userGroup, user, role); + store.removeRoleFromUser(user.getName(), role); + } + + /** Test checkIfOtherUsersHaveRole with user null. */ + private void testWithUserNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) + throws UserManagementException { + Role role = new RoleImpl(roleDefinition, userGroup, null); + store.addRoleForUser(user.getName(), role); + assertThatNoUserHasWildcardRole(userGroup, user, role); + store.removeRoleFromUser(user.getName(), role); + } + + /** Test checkIfOtherUsersHaveRole with tenant null. */ + private void testWithTenantNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) + throws UserManagementException { + Role role = new RoleImpl(roleDefinition, null, user); + store.addRoleForUser(user.getName(), role); + assertThatNoUserHasWildcardRole(userGroup, user, role); + store.removeRoleFromUser(user.getName(), role); + } + + /** assert that no user has a wildcard role checkIfOtherUsersHaveRole with both tenant and user not null. */ + private void assertThatNoUserHasWildcardRole(UserGroup userGroup, User user, Role role) + throws UserManagementException { + Iterable rolesFromUser = store.getRolesFromUser(user.getName()); + // check that role was added correctly + assertFalse(Util.size(rolesFromUser) == 0); + assertTrue(Util.contains(rolesFromUser, role)); + + // check if other users have the role + Pair> result = store.getOtherUsersHaveRole(role); + assertFalse(result.getA()); + assertNotNull(result.getB()); + + // This stream effectively executes B.getUserOwner.getName for each ownership and filters null values to avoid + // exceptions. + List usernamesWithRole = result.getB().stream().map(Ownership::getUserOwner).filter(uo -> uo != null) + .map(SecurityUser::getName).filter(su -> su != null).collect(Collectors.toList()); + + // Similar to above just with B.getTenantOwner.getId. + List groupNamesWithRole = result.getB().stream().map(Ownership::getTenantOwner).filter(uo -> uo != null) + .map(UserGroup::getId).filter(su -> su != null).collect(Collectors.toList()); + + // check that either one is not false + assertFalse(usernamesWithRole.isEmpty() && groupNamesWithRole.isEmpty()); + + if (!usernamesWithRole.isEmpty()) { + // list of usernames is not empty -> check if user name is contained in it + assertTrue(usernamesWithRole.contains(user.getName())); + } else if (!groupNamesWithRole.isEmpty()) { + // list of groupids is not empty -> check if group id is contained in it + assertTrue(groupNamesWithRole.contains(userGroup.getId())); + } + } } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index f6256f3606f..07cdeac9a27 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -18,6 +18,7 @@ import org.apache.shiro.SecurityUtils; import com.sap.sse.ServerInfo; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.security.PreferenceConverter; @@ -27,6 +28,7 @@ import com.sap.sse.security.UserImpl; import com.sap.sse.security.UserStore; import com.sap.sse.security.shared.Account; import com.sap.sse.security.shared.AdminRole; +import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.PredefinedRoles; import com.sap.sse.security.shared.Role; import com.sap.sse.security.shared.RoleDefinition; @@ -38,6 +40,7 @@ import com.sap.sse.security.shared.UserGroupManagementException; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UserRole; import com.sap.sse.security.shared.WildcardPermission; +import com.sap.sse.security.shared.impl.OwnershipImpl; import com.sap.sse.security.shared.impl.UserGroupImpl; import com.sap.sse.security.userstore.mongodb.impl.FieldNames.Tenant; @@ -702,6 +705,31 @@ public class UserStoreImpl implements UserStore { return result; } + @Override + public Pair> getOtherUsersHaveRole(Role roleToCheck) { + Set ownerships = new HashSet<>(); + for (User user : getUsers()) { + try { + for (Role role : getRolesFromUser(user.getName())) { + if (!role.equals(roleToCheck)) { + // wrong role + continue; + } + if (role.getQualifiedForTenant() == null && role.getQualifiedForUser() == null) { + // wildcard rule exists -> return A=true + return new Pair<>(true, null); + } else { + ownerships.add(new OwnershipImpl(role.getQualifiedForUser(), role.getQualifiedForTenant())); + } + } + } catch (UserManagementException e) { + // user did not exist -> should not happen + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + return new Pair<>(false, ownerships); + } + @Override public Iterable getRolesFromUser(String username) throws UserManagementException { if (users.get(username) == null) { diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java index 37afcbcd3a4..6090cadf33a 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java @@ -5,7 +5,9 @@ import java.util.Set; import java.util.UUID; import com.sap.sse.common.Named; +import com.sap.sse.common.Util.Pair; import com.sap.sse.security.shared.Account; +import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.Role; import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.SecurityUser; @@ -230,4 +232,12 @@ public interface UserStore extends UserGroupProvider, Named { * Ensures that the predefined role definitions, particularly the "admin" and the "user" role, exist. */ void ensureDefaultRolesExist(); + + /** + * @return a pair with:
+ * If A is true, at least one user has an unqualified version of the {@link #roleToCheck} (without tenant or + * user qualification). In this case, B is null.
+ * If A is false, B contains all the ownerships of {@link #roleToCheck} + */ + Pair> getOtherUsersHaveRole(Role roleToCheck); } From 9ee0ac67f2f40ea81d1833c3c1e2108fa31a2a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 16:00:21 +0100 Subject: [PATCH 15/42] fixed and simplified test setup --- .../domain/test/OfflineSerializationTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index 29614545281..36b5c1a1ae1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -47,15 +47,12 @@ import com.sap.sse.common.Color; import com.sap.sse.common.Duration; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.security.AccessControlStore; -import com.sap.sse.security.SecurityService; +import com.sap.sse.security.UserImpl; import com.sap.sse.security.UserStore; -import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.shared.User; import com.sap.sse.security.shared.UserGroup; import com.sap.sse.security.shared.UserGroupManagementException; import com.sap.sse.security.shared.UserManagementException; -import com.sap.sse.security.userstore.mongodb.AccessControlStoreImpl; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; public class OfflineSerializationTest extends AbstractSerializationTest { @@ -112,9 +109,13 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory receiverDomainFactory = new DomainFactoryImpl((srlid)->null); UserStore userStore = new UserStoreImpl("defaultTenant"); userStore.clear(); - AccessControlStore aclStore = new AccessControlStoreImpl(userStore); - SecurityService securityService = new SecurityServiceImpl(userStore, aclStore); - assertNotNull(securityService); + UserGroup defaultTenant = userStore.createUserGroup(UUID.randomUUID(), "admin-tenant"); + UserImpl user = userStore.createUser("admin", "", defaultTenant); + defaultTenant.add(user); + userStore.updateUserGroup(defaultTenant); + user.getDefaultTenantMap().put("testserver", defaultTenant); + userStore.updateUser(user); + { User admin = userStore.getUserByName("admin"); UserGroup adminTenant = admin.getDefaultTenant("testserver"); From 24915c23ece61dcd304002a8a2902c5245f33cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 16:28:17 +0100 Subject: [PATCH 16/42] added missing translation --- .../gwt/ui/adminconsole/LocalServerManagementPanel.java | 2 +- .../java/com/sap/sailing/gwt/ui/client/StringMessages.java | 3 +-- .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 3 ++- .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java index d7241931134..8f43320014f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java @@ -86,7 +86,7 @@ public class LocalServerManagementPanel extends SimplePanel { grid.setWidget(0, 0, new Label(stringMessages.standaloneServer() + ":")); grid.setWidget(0, 1, isStandaloneServerCheckbox); - grid.setWidget(1, 0, new Label(stringMessages.isPublic() + ":")); + grid.setWidget(1, 0, new Label(stringMessages.publicServer() + ":")); grid.setWidget(1, 1, isPublicServer); grid.setWidget(2, 0, new Label(stringMessages.selfServiceServer() + ":")); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 437bdcb5516..ffacaef989b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -2197,8 +2197,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String noStatisticsFoundForCompetitors(); String sailorProfileRemoveMessage(); String showInDataMining(); - String confirmDeletion(); - String selfServiceServer(); + String publicServer(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 5c6781bec7d..3740ed65506 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -2194,4 +2194,5 @@ sailorProfileRemoveMessage=Are you sure you want to remove this Sailor Profile? moreLoginInformationSectionSailorProfilesDescription=After logging in, you can now find a new area under your user profile, which is called 'Sailor Profiles'. You can add some competitors to such a Sailor Profile and get more insights about these competitors. In a Sailor Profile, you can see all the events your favorite competitors participated in and compare their placements to each others. Additionally, you get an overview about the most important statistics, like the maximum speed or the average distance to the start line with direct links to the Race Map to see the exact point in time, where for example the fastest competitor reached his record. For aggregative statistics like the average distance to startline at racestart, you can click on the arrow and are forwarded to Data Mining where you can drill down on the average values. moreLoginInformationSectionSailorProfilesHeading=Sailor Profiles confirmDeletion=Confirm Deletion -selfServiceServer=Server has Self-Service \ No newline at end of file +selfServiceServer=Server has Self-Service +publicServer=Server is public \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 017486c393f..9aa18a29ecd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -2188,4 +2188,6 @@ pleaseSelectCompetitorFirst=Bitte zunächst ein Team auswählen sailorProfileRemoveMessage=Sicher, dass das Seglerprofil gelöscht werden soll? moreLoginInformationSectionSailorProfilesDescription=Nach dem Login können Sie nun in ihrem Userprofil einen neuen Bereich namens 'Seglerprofile' finden. Dort können Sie ein solches Seglerprofil anlegen, um detailliertere Informationen zum ausgewählten Team zu erhalten. In einem Seglerprofil können Sie alle Events ihrer Lieblingsteams verfolgen und deren Platzierungen direkt miteinander vergleichen. Außerdem erhalten Sie eine Übersicht über die wichtigsten Statistiken, wie die Maximalgeschwindigkeit oder die durchschnittliche Distanz zur Startlinie am Rennstart. Von hier aus gibt es auch direkte Absprungpunkte zu den für die individuelle Statistik relevanten Orten in Sailing Analytics. So können Sie etwa direkt von der Maximalgeschwindigkeit in die Race Map springen, wo dann das Rennen zu dem Zeitpunkt der Maximalgeschwindigkeit startet. Von der durchschnittlichen Distanz zur Startlinie können Sie dagegen gleich ins Data Mining wechseln, wo Sie dann einen Drill Down der Durchschnittswerte sehen. moreLoginInformationSectionSailorProfilesHeading=Seglerprofile -confirmDeletion=Löschen bestätigen \ No newline at end of file +confirmDeletion=Löschen bestätigen +selfServiceServer=Self-Service Server +publicServer=Server ist öffentlich \ No newline at end of file From 4c7eac55e81f8862b1a7b65f2b9b83a5f38db3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 12 Nov 2018 16:56:40 +0100 Subject: [PATCH 17/42] simplified and fixed default tenant handling for createUser --- .../server/test/TaggingServiceTest.java | 3 ++- ...plicationLeadingToEmailReplicationTest.java | 5 +++-- .../test/SecurityServiceInitialLoadTest.java | 4 +++- .../test/SimpleSecurityReplicationTest.java | 12 ++++++++---- .../jaxrs/api/SecurityResourceTest.java | 5 ++++- .../com/sap/sse/security/SecurityService.java | 5 ----- .../sse/security/impl/SecurityServiceImpl.java | 18 +++--------------- .../security/jaxrs/api/SecurityResource.java | 5 ++++- 8 files changed, 27 insertions(+), 30 deletions(-) diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java index 35faf8dba66..599aa5f5643 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.fail; import java.io.IOException; import java.net.MalformedURLException; import java.util.List; +import java.util.Locale; import java.util.logging.Logger; import org.apache.shiro.SecurityUtils; @@ -89,7 +90,7 @@ public class TaggingServiceTest { // setup security service securityService = new SecurityBundleTestWrapper().initializeSecurityServiceForTesting(); // create & login user - securityService.createSimpleUser(username, email, password, fullName, company, null); + securityService.createSimpleUser(username, email, password, fullName, company, Locale.ENGLISH, null); subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username, password)); // setup tagging service diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java index 43216ed5d6f..fbf50c7267f 100644 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.mock; import java.io.IOException; import java.net.MalformedURLException; +import java.util.Locale; import org.junit.Before; import org.junit.Ignore; @@ -106,7 +107,7 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe final String password = "BertMyFriend"; final String validationBaseURL = null; //so that validation email is not sent masterSecurityService.createSimpleUser(username, email, password, - /* fullName */ null, /* company */ null, validationBaseURL); + /* fullName */ null, /* company */ null, Locale.ENGLISH, validationBaseURL); masterSecurityService.sendMail(username, "subject", "body"); securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); @@ -132,7 +133,7 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe final String password = "BertMyFriend"; final String validationBaseURL = null; //so that validation email is not sent replicaSecurityService.createSimpleUser(username, email, password, - /* fullName */ null, /* company */ null, validationBaseURL); + /* fullName */ null, /* company */ null, Locale.ENGLISH, validationBaseURL); replicaSecurityService.sendMail(username, "subject", "body"); securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityServiceInitialLoadTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityServiceInitialLoadTest.java index 6028455d282..fbee2048e67 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityServiceInitialLoadTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityServiceInitialLoadTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; import java.net.MalformedURLException; +import java.util.Locale; import org.junit.Test; @@ -37,7 +38,8 @@ public class SecurityServiceInitialLoadTest extends AbstractServerWithSingleServ final AccessControlStore accessControlStore = new AccessControlStoreImpl(PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), userStore); final SecurityServiceImpl newMaster = new SecurityServiceImpl(userStore, accessControlStore); - newMaster.createSimpleUser(username, email, password, fullName, company, /* validationBaseURL */ null); + newMaster.createSimpleUser(username, email, password, fullName, company, + /* validationBaseURL */ Locale.ENGLISH, null); accessToken = newMaster.createAccessToken(username); return newMaster; } diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java index 719ee14763b..5b1b547606a 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java @@ -7,6 +7,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.util.Locale; + import org.junit.Test; import com.sap.sse.common.mail.MailException; @@ -25,7 +27,8 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe final String company = "Ernie's Company"; final String validationBaseURL = "http://me.to.back.com"; assertNull(master.getUserByName(username)); - UserImpl user = master.createSimpleUser(username, email, password, fullName, company, validationBaseURL); + UserImpl user = master.createSimpleUser(username, email, password, fullName, company, Locale.ENGLISH, + validationBaseURL); assertNotNull(user); assertSame(user, master.getUserByName(username)); assertTrue(master.checkPassword(username, password)); @@ -59,7 +62,8 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe final String validationBaseURL = "http://me.to.back.com"; final String fullName = "Ernie's Full Name"; final String company = "Ernie's Company"; - UserImpl user = master.createSimpleUser(username, email, password, fullName, company, validationBaseURL); + UserImpl user = master.createSimpleUser(username, email, password, fullName, company, Locale.ENGLISH, + validationBaseURL); user.setFullName(fullName); user.setCompany(company); final String emailValidationSecretAfterCreation = user.getValidationSecret(); @@ -88,7 +92,7 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe final String newPassword = "ErnieAndBert"; final String validationBaseURL = "http://me.to.back.com"; master.createSimpleUser(username, email, password, - /* fullName */ null, /* company */ null, validationBaseURL); + /* fullName */ null, /* company */ null, Locale.ENGLISH, validationBaseURL); master.updateSimpleUserPassword(username, newPassword); assertTrue(master.checkPassword(username, newPassword)); @@ -110,7 +114,7 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe final String validationBaseURL = "http://me.to.back.com/validateemail"; final String passwordResetBaseURL = "http://me.to.back.com/passwordreset"; UserImpl user = master.createSimpleUser(username, email, password, - /* fullName */ null, /* company */ null, validationBaseURL); + /* fullName */ null, /* company */ null, Locale.ENGLISH, validationBaseURL); master.validateEmail(username, user.getValidationSecret()); assertTrue(user.isEmailValidated()); master.resetPassword(username, passwordResetBaseURL); diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java index 71e78dd644d..b5e2017078a 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java @@ -6,6 +6,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.Locale; + import javax.ws.rs.core.Response; import org.apache.shiro.SecurityUtils; @@ -55,7 +57,8 @@ public class SecurityResourceTest { store, accessControlStore, /* hasPermissionsProvider */null, /* setAsActivatorSecurityService */ true); SecurityUtils.setSecurityManager(service.getSecurityManager()); - service.createSimpleUser(USERNAME, "a@b.c", PASSWORD, "The User", "SAP SE", /* validation URL */ null); + service.createSimpleUser(USERNAME, "a@b.c", PASSWORD, "The User", "SAP SE", + /* validation URL */ Locale.ENGLISH, null); authenticatedAdmin = SecurityUtils.getSubject(); authenticatedAdmin.login(new UsernamePasswordToken(USERNAME, PASSWORD)); Session session = authenticatedAdmin.getSession(); 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 7672a06cf25..6053eb746e9 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 @@ -149,11 +149,6 @@ public interface SecurityService extends ReplicableWithObjectInputStreamnull, no validation will be attempted - */ - SecurityUser createSimpleUser(String username, String email, String password, String fullName, String company, String validationBaseURL) throws UserManagementException, MailException, UserGroupManagementException; - /** * @param validationBaseURL if null, no validation will be attempted */ 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 27ca8650ddd..5af0eff165a 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 @@ -264,8 +264,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat logger.info("No users found, creating default user \""+ADMIN_USERNAME+"\" with password \""+ADMIN_DEFAULT_PASSWORD+"\""); final User adminUser = createSimpleUser(ADMIN_USERNAME, "nobody@sapsailing.com", ADMIN_DEFAULT_PASSWORD, - /* fullName */ null, /* company */ null, Locale.ENGLISH, /* validationBaseURL */ null, - getDefaultTenant()); + /* fullName */ null, /* company */ null, Locale.ENGLISH, /* validationBaseURL */ null); apply(s -> s.internalSetOwnership( SecuredSecurityTypes.USER.getQualifiedObjectIdentifier(ADMIN_USERNAME), ADMIN_USERNAME, null, @@ -688,22 +687,10 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat return userStore.getUserByEmail(email); } - @Override - public UserImpl createSimpleUser(final String username, final String email, String password, String fullName, - String company, final String validationBaseURL) throws UserManagementException, MailException, UserGroupManagementException { - return createSimpleUser(username, email, password, fullName, company, null, validationBaseURL); - } - @Override public UserImpl createSimpleUser(final String username, final String email, String password, String fullName, String company, Locale locale, final String validationBaseURL) throws UserManagementException, MailException, UserGroupManagementException { - return createSimpleUser(username, email, password, fullName, company, /* locale */ null, validationBaseURL, - getDefaultTenantForCurrentUser()); - } - - private UserImpl createSimpleUser(final String username, final String email, String password, String fullName, - String company, Locale locale, final String validationBaseURL, UserGroup tenantOwner) throws UserManagementException, MailException, UserGroupManagementException { logger.info("Creating user "+username); if (userStore.getUserByName(username) != null) { logger.warning("User "+username+" already exists"); @@ -733,7 +720,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat // the new user becomes its owner to ensure the user role is correctly working // the default tenant is the owning tenant to allow users having admin role for a specific server tenant to also be able to delete users - accessControlStore.setOwnership(SecuredSecurityTypes.USER.getQualifiedObjectIdentifier(username), result, getDefaultTenant(), username); + accessControlStore.setOwnership(SecuredSecurityTypes.USER.getQualifiedObjectIdentifier(username), result, + getDefaultTenantForCurrentUser(), username); // the new user becomes the owning user of its own specific tenant which initially only contains the new user accessControlStore.setOwnership(SecuredSecurityTypes.USER_GROUP.getQualifiedObjectIdentifier(tenant.getId().toString()), result, tenant, tenant.getName()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index f0679527abf..c8c06aaa21b 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -1,5 +1,7 @@ package com.sap.sse.security.jaxrs.api; +import java.util.Locale; + import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -119,7 +121,8 @@ public class SecurityResource extends AbstractSecurityResource { username, () -> { try { final String validationBaseURL = getEmailValidationBaseURL(uriInfo); - getService().createSimpleUser(username, email, password, fullName, company, validationBaseURL); + getService().createSimpleUser(username, email, password, fullName, company, Locale.ENGLISH, + validationBaseURL); SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password)); return respondWithAccessTokenForUser(username); } catch (UserManagementException | MailException | UserGroupManagementException e) { From 42490c4a616986f5865c5f9115c0051965542d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 16:33:52 +0100 Subject: [PATCH 18/42] Adjusted parameter type (Role vs RoleDefinition) --- .../test/UserStoreWithPersistenceTest.java | 19 ++++++++++++------- .../userstore/mongodb/UserStoreImpl.java | 6 +++--- .../src/com/sap/sse/security/UserStore.java | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java index 32babbe941a..f67d117f2c2 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java @@ -30,6 +30,7 @@ import com.sap.sse.security.UserImpl; import com.sap.sse.security.UserStore; import com.sap.sse.security.shared.Ownership; import com.sap.sse.security.shared.Role; +import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.RoleDefinitionImpl; import com.sap.sse.security.shared.RoleImpl; import com.sap.sse.security.shared.SecurityUser; @@ -257,7 +258,7 @@ public class UserStoreWithPersistenceTest { Iterable rolesFromUser = store.getRolesFromUser(user.getName()); assertFalse(Util.size(rolesFromUser) == 0); assertTrue(Util.contains(rolesFromUser, role)); - Pair> result = store.getOtherUsersHaveRole(role); + Pair> result = store.getOtherUsersHaveRole(roleDefinition); assertTrue(result.getA()); assertNull(result.getB()); @@ -269,7 +270,7 @@ public class UserStoreWithPersistenceTest { throws UserManagementException { Role role = new RoleImpl(roleDefinition, userGroup, user); store.addRoleForUser(user.getName(), role); - assertThatNoUserHasWildcardRole(userGroup, user, role); + assertThatNoUserHasWildcardRole(userGroup, user, roleDefinition); store.removeRoleFromUser(user.getName(), role); } @@ -278,7 +279,7 @@ public class UserStoreWithPersistenceTest { throws UserManagementException { Role role = new RoleImpl(roleDefinition, userGroup, null); store.addRoleForUser(user.getName(), role); - assertThatNoUserHasWildcardRole(userGroup, user, role); + assertThatNoUserHasWildcardRole(userGroup, user, roleDefinition); store.removeRoleFromUser(user.getName(), role); } @@ -287,20 +288,24 @@ public class UserStoreWithPersistenceTest { throws UserManagementException { Role role = new RoleImpl(roleDefinition, null, user); store.addRoleForUser(user.getName(), role); - assertThatNoUserHasWildcardRole(userGroup, user, role); + assertThatNoUserHasWildcardRole(userGroup, user, roleDefinition); store.removeRoleFromUser(user.getName(), role); } /** assert that no user has a wildcard role checkIfOtherUsersHaveRole with both tenant and user not null. */ - private void assertThatNoUserHasWildcardRole(UserGroup userGroup, User user, Role role) + private void assertThatNoUserHasWildcardRole(UserGroup userGroup, User user, RoleDefinition roleDefinition) throws UserManagementException { Iterable rolesFromUser = store.getRolesFromUser(user.getName()); // check that role was added correctly assertFalse(Util.size(rolesFromUser) == 0); - assertTrue(Util.contains(rolesFromUser, role)); + boolean containsRole = false; + for (Role role : rolesFromUser) { + containsRole |= role.getRoleDefinition().equals(roleDefinition); + } + assertTrue(containsRole); // check if other users have the role - Pair> result = store.getOtherUsersHaveRole(role); + Pair> result = store.getOtherUsersHaveRole(roleDefinition); assertFalse(result.getA()); assertNotNull(result.getB()); diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 07cdeac9a27..4b1596788a3 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -706,12 +706,12 @@ public class UserStoreImpl implements UserStore { } @Override - public Pair> getOtherUsersHaveRole(Role roleToCheck) { - Set ownerships = new HashSet<>(); + public Pair> getOtherUsersHaveRole(RoleDefinition roleToCheck) { + final Set ownerships = new HashSet<>(); for (User user : getUsers()) { try { for (Role role : getRolesFromUser(user.getName())) { - if (!role.equals(roleToCheck)) { + if (!role.getRoleDefinition().equals(roleToCheck)) { // wrong role continue; } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java index 6090cadf33a..e917e241d10 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java @@ -239,5 +239,5 @@ public interface UserStore extends UserGroupProvider, Named { * user qualification). In this case, B is null.
* If A is false, B contains all the ownerships of {@link #roleToCheck} */ - Pair> getOtherUsersHaveRole(Role roleToCheck); + Pair> getOtherUsersHaveRole(RoleDefinition roleToCheck); } From 43e505d74d9b1b5f6edcd077f9b4c897fb4276fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Mon, 12 Nov 2018 17:29:48 +0100 Subject: [PATCH 19/42] Implemented wildcard permission check for all existing qualification when updating an existing role --- .../ui/server/UserManagementServiceImpl.java | 10 ++++------ .../com/sap/sse/security/SecurityService.java | 10 ++++++++++ .../sse/security/impl/SecurityServiceImpl.java | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) 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 85095c33c32..1b5d87a689c 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 @@ -129,12 +129,10 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U } Set addedPermissions = new HashSet<>(roleDefinitionWithNewProperties.getPermissions()); addedPermissions.removeAll(existingRole.getPermissions()); - for (WildcardPermission permissionToAdd : addedPermissions) { - // FIXME check based on actually existing qualifications - if (!getSecurityService().hasCurrentUserMetaPermission(permissionToAdd, null)) { - throw new UnauthorizedException("Not permitted to grant permission " + permissionToAdd + " for role " - + roleDefinitionWithNewProperties.getName()); - } + + if (!getSecurityService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(existingRole, addedPermissions)) { + throw new UnauthorizedException("Not permitted to grant permissions for role " + + roleDefinitionWithNewProperties.getName()); } getSecurityService().updateRoleDefinition(roleDefinitionWithNewProperties); 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 6053eb746e9..5516073440c 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 @@ -372,4 +372,14 @@ public interface SecurityService extends ReplicableWithObjectInputStream permissionsToCheck); } 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 5af0eff165a..479556dc215 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 @@ -1858,4 +1858,21 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat final List alreadyMigrated = getMigrationInfoForKey(permissions); alreadyMigrated.add(typeName); } + + @Override + public boolean hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(RoleDefinition role, + Iterable permissionsToCheck) { + Pair> qualificationsToCheck = userStore.getOtherUsersHaveRole(role); + final Iterable effectiveQualificationsToCheck = Boolean.TRUE.equals(qualificationsToCheck.getA()) + ? Collections.singletonList(null) + : qualificationsToCheck.getB(); + for (WildcardPermission permission : permissionsToCheck) { + for (Ownership ownership : effectiveQualificationsToCheck) { + if (!hasCurrentUserMetaPermission(permission, ownership)) { + return false; + } + } + } + return true; + } } From 560bf2fc2d6ce59c5b70770aad236c772f019332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Tue, 13 Nov 2018 08:53:19 +0100 Subject: [PATCH 20/42] Moved SailingViewerRole to allow access from gwt.ui bundle --- .../src/com/sap/sailing/server/impl/Activator.java | 1 + .../sailing/server/{impl => security}/SailingViewerRole.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename java/com.sap.sailing.server/src/com/sap/sailing/server/{impl => security}/SailingViewerRole.java (96%) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index abc7a924181..1bd969019f8 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -43,6 +43,7 @@ import com.sap.sailing.server.impl.preferences.model.CompetitorNotificationPrefe import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences; import com.sap.sailing.server.impl.preferences.model.StoredDataMiningQueryPreferences; import com.sap.sailing.server.notification.impl.SailingNotificationServiceImpl; +import com.sap.sailing.server.security.SailingViewerRole; import com.sap.sailing.server.statistics.TrackedRaceStatisticsCache; import com.sap.sailing.server.statistics.TrackedRaceStatisticsCacheImpl; import com.sap.sse.MasterDataImportClassLoaderService; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/SailingViewerRole.java similarity index 96% rename from java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java rename to java/com.sap.sailing.server/src/com/sap/sailing/server/security/SailingViewerRole.java index 32ca226c59c..c01cd298acb 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/SailingViewerRole.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/SailingViewerRole.java @@ -1,4 +1,4 @@ -package com.sap.sailing.server.impl; +package com.sap.sailing.server.security; import com.sap.sailing.domain.common.security.SecuredDomainType; import com.sap.sse.security.shared.HasPermissions.DefaultActions; From 4dd2cd7386752e28c8810617da52d26f228086a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Tue, 13 Nov 2018 08:54:54 +0100 Subject: [PATCH 21/42] Consistently using ServerInfo.getName() to get the server name --- ...earerTokenOrBasicOrFormAuthenticationFilterSAPSailing.java | 4 ++-- ...rBasicOrFormOrAnonymousAuthenticationFilterSAPSailing.java | 4 ++-- .../sap/sse/security/userstore/mongodb/impl/Activator.java | 4 ++-- .../src/com/sap/sse/security/impl/SecurityServiceImpl.java | 3 +-- java/com.sap.sse/src/com/sap/sse/ServerInfo.java | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing.java index 20b6204a183..d802f3a8618 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing.java @@ -1,11 +1,11 @@ package com.sap.sailing.server.security; -import com.sap.sse.ServerStartupConstants; +import com.sap.sse.ServerInfo; import com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter; public class BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing extends BearerTokenOrBasicOrFormAuthenticationFilter { public BearerTokenOrBasicOrFormAuthenticationFilterSAPSailing() { - super("SAP Sailing Analytics ("+/* application name */ ServerStartupConstants.SERVER_NAME+")"); + super("SAP Sailing Analytics ("+/* application name */ ServerInfo.getName()+")"); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing.java index 3b2b006ff47..22d321c6662 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/security/BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing.java @@ -3,14 +3,14 @@ package com.sap.sailing.server.security; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import com.sap.sse.ServerStartupConstants; +import com.sap.sse.ServerInfo; import com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter; public class BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing extends BearerTokenOrBasicOrFormAuthenticationFilter { public BearerTokenOrBasicOrFormOrAnonymousAuthenticationFilterSAPSailing() { - super("SAP Sailing Analytics (" + /* application name */ ServerStartupConstants.SERVER_NAME + ")"); + super("SAP Sailing Analytics (" + /* application name */ ServerInfo.getName() + ")"); } @Override diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java index d512ff77df5..bf77e0ef3c5 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java @@ -8,7 +8,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; -import com.sap.sse.ServerStartupConstants; +import com.sap.sse.ServerInfo; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.security.AccessControlStore; import com.sap.sse.security.PreferenceConverterRegistrationManager; @@ -39,7 +39,7 @@ public class Activator implements BundleActivator { public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; logger.info("Creating user store"); - final String defaultTenantName = System.getProperty(UserStore.DEFAULT_TENANT_NAME_PROPERTY_NAME, ServerStartupConstants.SERVER_NAME); + final String defaultTenantName = System.getProperty(UserStore.DEFAULT_TENANT_NAME_PROPERTY_NAME, ServerInfo.getName()); final UserStoreImpl userStore = new UserStoreImpl(defaultTenantName); AccessControlStoreImpl accessControlStore = new AccessControlStoreImpl(userStore); accessControlStoreRegistration = context.registerService(AccessControlStore.class.getName(), 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 479556dc215..af7ee34d8ef 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 @@ -75,7 +75,6 @@ import org.scribe.model.Token; import org.scribe.oauth.OAuthService; import com.sap.sse.ServerInfo; -import com.sap.sse.ServerStartupConstants; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.mail.MailException; @@ -1529,7 +1528,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat } if (checkCreateObjectOnServer) { SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.SERVER - .getStringPermissionForObjects(ServerActions.CREATE_OBJECT, ServerStartupConstants.SERVER_NAME)); + .getStringPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName())); } SecurityUtils.getSubject() .checkPermission(identifier.getStringPermission(DefaultActions.CREATE)); diff --git a/java/com.sap.sse/src/com/sap/sse/ServerInfo.java b/java/com.sap.sse/src/com/sap/sse/ServerInfo.java index 6300fab7489..294f2b70529 100644 --- a/java/com.sap.sse/src/com/sap/sse/ServerInfo.java +++ b/java/com.sap.sse/src/com/sap/sse/ServerInfo.java @@ -15,7 +15,7 @@ public class ServerInfo { private static final Logger logger = Logger.getLogger(ServerInfo.class.getName()); public static String getBuildVersion() { - String version = "Unknown or Development (" + ServerStartupConstants.SERVER_NAME + ")"; + String version = "Unknown or Development (" + getName() + ")"; File versionfile = new File(ServerStartupConstants.JETTY_HOME + File.separator + "version.txt"); if (versionfile.exists()) { try (BufferedReader bufferedReader = new BufferedReader(new FileReader(versionfile))) { From 237931333cc2eb25122b8b7f8f6a531a8aa3371f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Tue, 13 Nov 2018 08:56:38 +0100 Subject: [PATCH 22/42] Implemented server config updates and retrieval --- .../gwt/ui/server/SailingServiceImpl.java | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) 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 106a8138598..749043f4418 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 @@ -523,6 +523,7 @@ import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive; import com.sap.sailing.server.operationaltransformation.UpdateSeries; import com.sap.sailing.server.operationaltransformation.UpdateServerConfiguration; import com.sap.sailing.server.operationaltransformation.UpdateSpecificRegatta; +import com.sap.sailing.server.security.SailingViewerRole; import com.sap.sailing.server.simulation.SimulationService; import com.sap.sailing.server.util.WaitForTrackedRaceUtil; import com.sap.sailing.simulator.Path; @@ -590,8 +591,13 @@ import com.sap.sse.security.ActionWithResult; import com.sap.sse.security.SecurityService; import com.sap.sse.security.SessionUtils; import com.sap.sse.security.shared.HasPermissions.DefaultActions; +import com.sap.sse.security.shared.RoleDefinition; +import com.sap.sse.security.shared.RoleImpl; import com.sap.sse.security.shared.User; import com.sap.sse.security.shared.UserGroup; +import com.sap.sse.security.shared.WildcardPermission; +import com.sap.sse.security.shared.impl.SecuredSecurityTypes; +import com.sap.sse.security.shared.impl.SecuredSecurityTypes.ServerActions; import com.sap.sse.security.shared.impl.WildcardPermissionEncoder; import com.sap.sse.security.ui.server.SecurityDTOUtil; import com.sap.sse.security.ui.shared.SuccessInfo; @@ -4697,8 +4703,29 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void updateServerConfiguration(ServerConfigurationDTO serverConfiguration) { - SailingServerConfiguration newServerConfiguration = new SailingServerConfigurationImpl(serverConfiguration.isStandaloneServer()); - getService().apply(new UpdateServerConfiguration(newServerConfiguration)); + getService().apply(new UpdateServerConfiguration(new SailingServerConfigurationImpl(serverConfiguration.isStandaloneServer()))); + + final User allUser = getSecurityService().getAllUser(); + if (allUser != null) { + final WildcardPermission createObjectOnCurrentServerPermission = SecuredSecurityTypes.SERVER + .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName()); + if (serverConfiguration.isSelfService()) { + getSecurityService().addPermissionForUser(allUser.getName(), createObjectOnCurrentServerPermission); + } else { + getSecurityService().removePermissionFromUser(allUser.getName(), createObjectOnCurrentServerPermission); + } + final RoleDefinition viewerRole = getSecurityService() + .getRoleDefinition(SailingViewerRole.getInstance().getId()); + final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); + if (viewerRole != null && defaultServerTenant != null) { + final RoleImpl publicAccessForServerRole = new RoleImpl(viewerRole, defaultServerTenant, null); + if (serverConfiguration.isPublic()) { + getSecurityService().addRoleForUser(allUser.getName(), publicAccessForServerRole); + } else { + getSecurityService().removeRoleFromUser(allUser.getName(), publicAccessForServerRole); + } + } + } } @Override @@ -8452,12 +8479,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } private boolean isSelfServiceServer() { - // TODO Auto-generated method stub - return false; + final User allUser = getSecurityService().getAllUser(); + return allUser != null && Util.contains(allUser.getPermissions(), SecuredSecurityTypes.SERVER + .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName())); } private boolean isPublicServer() { - // TODO Auto-generated method stub - return false; + final User allUser = getSecurityService().getAllUser(); + final RoleDefinition viewerRole = getSecurityService() + .getRoleDefinition(SailingViewerRole.getInstance().getId()); + final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); + return allUser != null && viewerRole != null && defaultServerTenant != null + && allUser.hasRole(new RoleImpl(viewerRole, defaultServerTenant, null)); } } From f57a984a6dac62c7c917d7ff2620db974322b649 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 13 Nov 2018 09:16:24 +0100 Subject: [PATCH 23/42] renamed 'getOtherUsersHaveRole'-method in UserStore to 'getExistingQualificationsForRoleDefinition' --- .../test/UserStoreWithPersistenceTest.java | 20 +++++++++++-------- .../userstore/mongodb/UserStoreImpl.java | 2 +- .../src/com/sap/sse/security/UserStore.java | 2 +- .../security/impl/SecurityServiceImpl.java | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java index f67d117f2c2..c34033abbf9 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/UserStoreWithPersistenceTest.java @@ -230,7 +230,8 @@ public class UserStoreWithPersistenceTest { } @Test - public void testGetOtherUsersHaveRole() throws UserManagementException, UserGroupManagementException { + public void testGetExistingQualificationsForRoleDefinition() + throws UserManagementException, UserGroupManagementException { User user = store.createUser("def", "d@test.de", store.createUserGroup(UUID.randomUUID(), "def-tentant")); RoleDefinitionImpl roleDefinition = new RoleDefinitionImpl(UUID.randomUUID(), "My-Test-Role"); store.createRoleDefinition(roleDefinition.getId(), roleDefinition.getName(), new ArrayList<>()); @@ -249,7 +250,7 @@ public class UserStoreWithPersistenceTest { testWithTenantAndUserNull(user, roleDefinition); } - /** Test getOtherUsersHaveRole with both tenant and user null. */ + /** Test getExistingQualificationsForRoleDefinition with both tenant and user null. */ private void testWithTenantAndUserNull(User user, RoleDefinitionImpl roleDefinition) throws UserManagementException { Role role = new RoleImpl(roleDefinition, null, null); @@ -258,14 +259,14 @@ public class UserStoreWithPersistenceTest { Iterable rolesFromUser = store.getRolesFromUser(user.getName()); assertFalse(Util.size(rolesFromUser) == 0); assertTrue(Util.contains(rolesFromUser, role)); - Pair> result = store.getOtherUsersHaveRole(roleDefinition); + Pair> result = store.getExistingQualificationsForRoleDefinition(roleDefinition); assertTrue(result.getA()); assertNull(result.getB()); store.removeRoleFromUser(user.getName(), role); } - /** Test checkIfOtherUsersHaveRole with both tenant and user not null. */ + /** Test getExistingQualificationsForRoleDefinition with both tenant and user not null. */ private void testWithTenantAndUserNotNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) throws UserManagementException { Role role = new RoleImpl(roleDefinition, userGroup, user); @@ -274,7 +275,7 @@ public class UserStoreWithPersistenceTest { store.removeRoleFromUser(user.getName(), role); } - /** Test checkIfOtherUsersHaveRole with user null. */ + /** Test getExistingQualificationsForRoleDefinition with user null. */ private void testWithUserNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) throws UserManagementException { Role role = new RoleImpl(roleDefinition, userGroup, null); @@ -283,7 +284,7 @@ public class UserStoreWithPersistenceTest { store.removeRoleFromUser(user.getName(), role); } - /** Test checkIfOtherUsersHaveRole with tenant null. */ + /** Test getExistingQualificationsForRoleDefinition with tenant null. */ private void testWithTenantNull(User user, RoleDefinitionImpl roleDefinition, UserGroup userGroup) throws UserManagementException { Role role = new RoleImpl(roleDefinition, null, user); @@ -292,7 +293,10 @@ public class UserStoreWithPersistenceTest { store.removeRoleFromUser(user.getName(), role); } - /** assert that no user has a wildcard role checkIfOtherUsersHaveRole with both tenant and user not null. */ + /** + * assert that no user has a wildcard role getExistingQualificationsForRoleDefinition with both tenant and user not + * null. + */ private void assertThatNoUserHasWildcardRole(UserGroup userGroup, User user, RoleDefinition roleDefinition) throws UserManagementException { Iterable rolesFromUser = store.getRolesFromUser(user.getName()); @@ -305,7 +309,7 @@ public class UserStoreWithPersistenceTest { assertTrue(containsRole); // check if other users have the role - Pair> result = store.getOtherUsersHaveRole(roleDefinition); + Pair> result = store.getExistingQualificationsForRoleDefinition(roleDefinition); assertFalse(result.getA()); assertNotNull(result.getB()); diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 4b1596788a3..fb8eab4b1c4 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -706,7 +706,7 @@ public class UserStoreImpl implements UserStore { } @Override - public Pair> getOtherUsersHaveRole(RoleDefinition roleToCheck) { + public Pair> getExistingQualificationsForRoleDefinition(RoleDefinition roleToCheck) { final Set ownerships = new HashSet<>(); for (User user : getUsers()) { try { diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java index e917e241d10..592b711e216 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java @@ -239,5 +239,5 @@ public interface UserStore extends UserGroupProvider, Named { * user qualification). In this case, B is null.
* If A is false, B contains all the ownerships of {@link #roleToCheck} */ - Pair> getOtherUsersHaveRole(RoleDefinition roleToCheck); + Pair> getExistingQualificationsForRoleDefinition(RoleDefinition roleToCheck); } 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 af7ee34d8ef..0d82abe2780 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 @@ -1861,7 +1861,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat @Override public boolean hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(RoleDefinition role, Iterable permissionsToCheck) { - Pair> qualificationsToCheck = userStore.getOtherUsersHaveRole(role); + Pair> qualificationsToCheck = userStore.getExistingQualificationsForRoleDefinition(role); final Iterable effectiveQualificationsToCheck = Boolean.TRUE.equals(qualificationsToCheck.getA()) ? Collections.singletonList(null) : qualificationsToCheck.getB(); From 9855994319b82e2b010957b94bc4638d19e1f6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 11:09:08 +0100 Subject: [PATCH 24/42] added proper logic for the server configuration panel --- .../LocalServerManagementPanel.java | 59 ++++++++++---- .../sailing/gwt/ui/client/StringMessages.java | 2 + .../gwt/ui/client/StringMessages.properties | 4 +- .../ui/client/StringMessages_de.properties | 4 +- .../gwt/ui/server/SailingServiceImpl.java | 78 ++++++++++++------- .../gwt/ui/shared/ServerConfigurationDTO.java | 10 +-- 6 files changed, 110 insertions(+), 47 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java index 8f43320014f..a1e8d455c8d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LocalServerManagementPanel.java @@ -13,6 +13,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.ServerInfoDTO; public class LocalServerManagementPanel extends SimplePanel { @@ -25,8 +27,8 @@ public class LocalServerManagementPanel extends SimplePanel { private CheckBox isStandaloneServerCheckbox; private Label serverNameLabel; private Label buildVersionLabel; - private CheckBox isPublicServer; - private CheckBox isSelfServiceServer; + private CheckBox isPublicServerCheckbox; + private CheckBox isSelfServiceServerCheckbox; public LocalServerManagementPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { @@ -77,20 +79,33 @@ public class LocalServerManagementPanel extends SimplePanel { } }); - isPublicServer = new CheckBox(); - isPublicServer.setEnabled(false); - isSelfServiceServer = new CheckBox(); - isSelfServiceServer.setEnabled(false); + isPublicServerCheckbox = new CheckBox(); + isPublicServerCheckbox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + serverConfigurationChanged(); + } + }); + isPublicServerCheckbox.setEnabled(false); + + isSelfServiceServerCheckbox = new CheckBox(); + isSelfServiceServerCheckbox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + serverConfigurationChanged(); + } + }); + isSelfServiceServerCheckbox.setEnabled(false); Grid grid = new Grid(3, 2); grid.setWidget(0, 0, new Label(stringMessages.standaloneServer() + ":")); grid.setWidget(0, 1, isStandaloneServerCheckbox); grid.setWidget(1, 0, new Label(stringMessages.publicServer() + ":")); - grid.setWidget(1, 1, isPublicServer); + grid.setWidget(1, 1, isPublicServerCheckbox); grid.setWidget(2, 0, new Label(stringMessages.selfServiceServer() + ":")); - grid.setWidget(2, 1, isSelfServiceServer); + grid.setWidget(2, 1, isSelfServiceServerCheckbox); serverConfigurationContentPanel.add(grid); @@ -98,17 +113,24 @@ public class LocalServerManagementPanel extends SimplePanel { } private void serverConfigurationChanged() { + Boolean publicServer = isPublicServerCheckbox.isEnabled() ? isPublicServerCheckbox.getValue() : null; + // FIXME self service not yet supported + Boolean selfServiceServer = isSelfServiceServerCheckbox.isEnabled() ? isSelfServiceServerCheckbox.getValue() + : null; ServerConfigurationDTO serverConfig = new ServerConfigurationDTO(isStandaloneServerCheckbox.getValue(), - isPublicServer.getValue(), isSelfServiceServer.getValue()); - + publicServer, selfServiceServer); + sailingService.updateServerConfiguration(serverConfig, new AsyncCallback() { @Override public void onFailure(Throwable caught) { + Notification.notify(stringMessages.updatedServerSetupError(), NotificationType.ERROR); errorReporter.reportError(caught.getMessage()); + refreshServerConfiguration(); } @Override public void onSuccess(Void result) { + Notification.notify(stringMessages.updatedServerSetup(), NotificationType.SUCCESS); refreshServerConfiguration(); } }); @@ -148,8 +170,19 @@ public class LocalServerManagementPanel extends SimplePanel { } private void updateServerConfiguration(ServerConfigurationDTO result) { - isStandaloneServerCheckbox.setValue(result.isStandaloneServer(), true); - isPublicServer.setValue(result.isPublic(), true); - isSelfServiceServer.setValue(result.isSelfService(), true); + isStandaloneServerCheckbox.setValue(result.isStandaloneServer(), false); + if (result.isPublic() != null) { + isPublicServerCheckbox.setEnabled(true); + isPublicServerCheckbox.setValue(result.isPublic(), false); + } else { + isPublicServerCheckbox.setEnabled(false); + } + if (result.isSelfService() != null) { + // isSelfServiceServerCheckbox.setEnabled(true); + isSelfServiceServerCheckbox.setValue(result.isSelfService(), false); + } else { + isSelfServiceServerCheckbox.setEnabled(false); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index ffacaef989b..2f61cf82f5d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -2200,4 +2200,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String confirmDeletion(); String selfServiceServer(); String publicServer(); + String updatedServerSetupError(); + String updatedServerSetup(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 3740ed65506..6da2578a581 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -2195,4 +2195,6 @@ moreLoginInformationSectionSailorProfilesDescription=After logging in, you can n moreLoginInformationSectionSailorProfilesHeading=Sailor Profiles confirmDeletion=Confirm Deletion selfServiceServer=Server has Self-Service -publicServer=Server is public \ No newline at end of file +publicServer=Server is public +updatedServerSetupError=Could not update Server Setup +updatedServerSetup=Server Setup updated \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 9aa18a29ecd..2eeda4704f4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -2190,4 +2190,6 @@ moreLoginInformationSectionSailorProfilesDescription=Nach dem Login können Sie moreLoginInformationSectionSailorProfilesHeading=Seglerprofile confirmDeletion=Löschen bestätigen selfServiceServer=Self-Service Server -publicServer=Server ist öffentlich \ No newline at end of file +publicServer=Server ist öffentlich +updatedServerSetupError=Konnte Server Konfiguration nicht anpassen +updatedServerSetup=Server Konfiguration angepasst \ No newline at end of file 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 749043f4418..7d273a0fe68 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 @@ -4700,30 +4700,43 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S isPublicServer(), isSelfServiceServer()); return result; } - + @Override public void updateServerConfiguration(ServerConfigurationDTO serverConfiguration) { - getService().apply(new UpdateServerConfiguration(new SailingServerConfigurationImpl(serverConfiguration.isStandaloneServer()))); - - final User allUser = getSecurityService().getAllUser(); - if (allUser != null) { - final WildcardPermission createObjectOnCurrentServerPermission = SecuredSecurityTypes.SERVER - .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName()); - if (serverConfiguration.isSelfService()) { - getSecurityService().addPermissionForUser(allUser.getName(), createObjectOnCurrentServerPermission); - } else { - getSecurityService().removePermissionFromUser(allUser.getName(), createObjectOnCurrentServerPermission); - } - final RoleDefinition viewerRole = getSecurityService() - .getRoleDefinition(SailingViewerRole.getInstance().getId()); - final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); - if (viewerRole != null && defaultServerTenant != null) { - final RoleImpl publicAccessForServerRole = new RoleImpl(viewerRole, defaultServerTenant, null); - if (serverConfiguration.isPublic()) { - getSecurityService().addRoleForUser(allUser.getName(), publicAccessForServerRole); - } else { - getSecurityService().removeRoleFromUser(allUser.getName(), publicAccessForServerRole); + getService().apply(new UpdateServerConfiguration( + new SailingServerConfigurationImpl(serverConfiguration.isStandaloneServer()))); + if (serverConfiguration.isPublic() != null || serverConfiguration.isSelfService() != null) { + final User allUser = getSecurityService().getAllUser(); + if (allUser != null) { + final WildcardPermission createObjectOnCurrentServerPermission = SecuredSecurityTypes.SERVER + .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName()); + if (serverConfiguration.isSelfService() != null) { + if (serverConfiguration.isSelfService()) { + getSecurityService().addPermissionForUser(allUser.getName(), + createObjectOnCurrentServerPermission); + } else { + getSecurityService().removePermissionFromUser(allUser.getName(), + createObjectOnCurrentServerPermission); + } } + if (serverConfiguration.isPublic() != null) { + final RoleDefinition viewerRole = getSecurityService() + .getRoleDefinition(SailingViewerRole.getInstance().getId()); + final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); + if (viewerRole != null && defaultServerTenant != null) { + final RoleImpl publicAccessForServerRole = new RoleImpl(viewerRole, defaultServerTenant, null); + if (serverConfiguration.isPublic()) { + getSecurityService().addRoleForUser(allUser.getName(), publicAccessForServerRole); + } else { + getSecurityService().removeRoleFromUser(allUser.getName(), publicAccessForServerRole); + } + } else { + throw new IllegalArgumentException("Viewerrole or defaultServerTenant is not existing"); + } + + } + } else { + throw new IllegalArgumentException("Alluser is not exiting"); } } } @@ -8478,18 +8491,29 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return tenants; } - private boolean isSelfServiceServer() { + private Boolean isSelfServiceServer() { + final Boolean result; final User allUser = getSecurityService().getAllUser(); - return allUser != null && Util.contains(allUser.getPermissions(), SecuredSecurityTypes.SERVER - .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName())); + if (allUser != null) { + result = Util.contains(allUser.getPermissions(), SecuredSecurityTypes.SERVER + .getPermissionForObjects(ServerActions.CREATE_OBJECT, ServerInfo.getName())); + } else { + result = null; + } + return result; } - private boolean isPublicServer() { + private Boolean isPublicServer() { + final Boolean result; final User allUser = getSecurityService().getAllUser(); final RoleDefinition viewerRole = getSecurityService() .getRoleDefinition(SailingViewerRole.getInstance().getId()); final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); - return allUser != null && viewerRole != null && defaultServerTenant != null - && allUser.hasRole(new RoleImpl(viewerRole, defaultServerTenant, null)); + if (allUser != null && viewerRole != null && defaultServerTenant != null) { + result = allUser.hasRole(new RoleImpl(viewerRole, defaultServerTenant, null)); + } else { + result = null; + } + return result; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java index 33808e852d3..fb9fc6633d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ServerConfigurationDTO.java @@ -4,14 +4,14 @@ import com.google.gwt.user.client.rpc.IsSerializable; public class ServerConfigurationDTO implements IsSerializable { private boolean isStandaloneServer; - private boolean isPublic; - private boolean isSelfService; + private Boolean isPublic; + private Boolean isSelfService; // for GWT ServerConfigurationDTO() { } - public ServerConfigurationDTO(boolean isStandaloneServer, boolean isPublic, boolean isSelfService) { + public ServerConfigurationDTO(boolean isStandaloneServer, Boolean isPublic, Boolean isSelfService) { this.isStandaloneServer = isStandaloneServer; this.isPublic = isPublic; this.isSelfService = isSelfService; @@ -21,11 +21,11 @@ public class ServerConfigurationDTO implements IsSerializable { return isStandaloneServer; } - public boolean isPublic() { + public Boolean isPublic() { return isPublic; } - public boolean isSelfService() { + public Boolean isSelfService() { return isSelfService; } } From c43947618f810a2d18f641632f894a66118583a5 Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Tue, 13 Nov 2018 14:16:13 +0100 Subject: [PATCH 25/42] Removed unused DOM elements from mobile stage partial --- .../gwt/home/mobile/partials/stage/Stage.ui.xml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.ui.xml index 84c5941b80c..42c59b717ab 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.ui.xml @@ -2,21 +2,18 @@ - - - + + +
-
-
-
+ + +
From 2989097cf831a07bc31abc29e0018ba34a196235 Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Tue, 13 Nov 2018 14:17:21 +0100 Subject: [PATCH 26/42] Improved styling and behavior of "what's new" dialog --- .../home/shared/partials/dialog/Dialog.gss | 26 +++++++------------ .../dialog/TwoOptionsDialogPanel.java | 15 ++++------- .../whatsnew/WhatsNewDialogFactory.java | 9 +++++-- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss index ee93e6bcfe0..11ee2514bea 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss @@ -6,7 +6,7 @@ padding: 1em; border: 1px solid #ccc; border-radius: 3px; - width: 25%; + width: 30em; -webkit-transform: translate(-50%, -50%); -moz-transform: translate(-50%, -50%); -ms-transform: translate(-50%, -50%); @@ -15,8 +15,7 @@ } .buttonAdjustments { - margin-left: .5em; - margin-top: .5em; + margin: .5em .25em 0; min-width: 11em; } @@ -48,6 +47,7 @@ top: 0; left: 0; background-color: rgba(0, 0, 0, .33333333); + z-index: 9999; } .title { @@ -57,20 +57,12 @@ margin-bottom: .75em; } -@media ( max-width: 400px) { +@media (max-width: 425px) { .dialog { - width: 90%; + width: calc(100% - 1em); + } + + .buttonAdjustments { + min-width: 100%; } } - -@media ( min-width: 400px) { - .dialog { - width: 50%; - } -} - -@media ( min-width: 600px) { - .dialog { - width: 40%; - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/TwoOptionsDialogPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/TwoOptionsDialogPanel.java index d51ad792077..5be26a9b6ce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/TwoOptionsDialogPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/TwoOptionsDialogPanel.java @@ -36,29 +36,24 @@ public class TwoOptionsDialogPanel extends Composite { DivElement titleField; private final DialogCallback callback; - private final PopupPanel parent; - public TwoOptionsDialogPanel() { - this("", "", null, null); - } - - public TwoOptionsDialogPanel(String message, String title, DialogCallback callback, PopupPanel parent) { + TwoOptionsDialogPanel(String message, String title, DialogCallback callback, PopupPanel parent) { initWidget(uiBinder.createAndBindUi(this)); this.callback = callback; this.parent = parent; - messageField.setInnerText(message); - titleField.setInnerText(title); + this.messageField.setInnerText(message); + this.titleField.setInnerText(title); } /** Sets the labels of the {@link #firstButton} and {@link #secondButton}. */ - public void setButtonLabels(String firstButtonText, String secondButtonText) { + void setButtonLabels(String firstButtonText, String secondButtonText) { firstButton.setText(firstButtonText); secondButton.setText(secondButtonText); } /** Changes the color of the {@link #firstButton} to red. */ - public void setFirstButtonDestructive() { + void setFirstButtonDestructive() { firstButton.addStyleName(DialogResources.INSTANCE.css().destructiveButton()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/whatsnew/WhatsNewDialogFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/whatsnew/WhatsNewDialogFactory.java index 80fbbd000bf..b572bc2eecc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/whatsnew/WhatsNewDialogFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/whatsnew/WhatsNewDialogFactory.java @@ -24,6 +24,8 @@ public final class WhatsNewDialogFactory { private static final long THRESHOLD_WHATS_NEW = 10; private static final Logger LOG = Logger.getLogger(WhatsNewDialogFactory.class.getName()); + private static boolean isUserNotified = false; + private WhatsNewDialogFactory() { } @@ -38,10 +40,10 @@ public final class WhatsNewDialogFactory { /** Shows a What's New Dialog. */ private static void showWhatsNewDialog(PlaceController placeController, DialogCallback dialogCallback) { - - PopupPanel dialog = DialogFactory.createDialog(StringMessages.INSTANCE.whatsNewDialogMessage(), + final PopupPanel dialog = DialogFactory.createDialog(StringMessages.INSTANCE.whatsNewDialogMessage(), StringMessages.INSTANCE.whatsNewDialogTitle(), false, StringMessages.INSTANCE.showChangelog(), StringMessages.INSTANCE.cancel(), dialogCallback); + isUserNotified = true; dialog.show(); } @@ -51,6 +53,9 @@ public final class WhatsNewDialogFactory { */ private static void showWhatsNewDialogIfNecessaryAndUpdatePreference(UserService userService, PlaceController placeController) { + if (isUserNotified) { + return; + } final long charactersInWhatsChangedDocument = WhatsNewResources.INSTANCE.getSailingAnalyticsNotesHtml() .getText().length(); userService.getPreference(WhatsNewSettings.PREF_NAME, new AsyncCallback() { From f923088e5bd5368bbcb7595a32d1786a4bac291b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 15:16:00 +0100 Subject: [PATCH 27/42] permission vertical related fix for MasterDataImportTest --- .../gateway/jaxrs/spi/MasterDataResource.java | 16 +- .../server/test/DummyMasterDataRessource.java | 14 ++ .../server/test/DummyRacingEventService.java | 27 +++ .../test/DummyRacingEventServiceImplMock.java | 26 +++ .../server/test/MasterDataImportTest.java | 212 ++++++++++-------- 5 files changed, 190 insertions(+), 105 deletions(-) create mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyMasterDataRessource.java create mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java create mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/spi/MasterDataResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/spi/MasterDataResource.java index cc8ed1d1f65..fd956947f4a 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/spi/MasterDataResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/spi/MasterDataResource.java @@ -36,6 +36,7 @@ import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.masterdataimport.TopLevelMasterData; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; +import com.sap.sse.security.SecurityService; import com.sap.sse.security.shared.User; @Path("/v1/masterdata/leaderboardgroups") @@ -49,7 +50,8 @@ public class MasterDataResource extends AbstractSailingServerResource { @QueryParam("compress") Boolean compress, @QueryParam("exportWind") Boolean exportWind, @QueryParam("exportDeviceConfigs") Boolean exportDeviceConfigs) throws UnsupportedEncodingException { - User user = getSecurityService().getCurrentUser(); + final SecurityService securityService = getSecurityService(); + User user = securityService.getCurrentUser(); if (user == null) { return Response.status(Status.FORBIDDEN).build(); } @@ -73,7 +75,7 @@ public class MasterDataResource extends AbstractSailingServerResource { if (requestedLeaderboardGroups.isEmpty()) { // add all visible for (LeaderboardGroup group : allLeaderboardGroups.values()) { - if (getSecurityService().hasCurrentUserReadPermission(group)) { + if (securityService.hasCurrentUserReadPermission(group)) { groupsToExport.add(group); } } @@ -82,7 +84,7 @@ public class MasterDataResource extends AbstractSailingServerResource { for (String name : requestedLeaderboardGroups) { LeaderboardGroup group = allLeaderboardGroups.get(name); if (group != null) { - if (getSecurityService().hasCurrentUserReadPermission(group)) { + if (securityService.hasCurrentUserReadPermission(group)) { groupsToExport.add(group); } } @@ -93,7 +95,7 @@ public class MasterDataResource extends AbstractSailingServerResource { for (LeaderboardGroup lg : groupsToExport) { for (Leaderboard leaderboard : lg.getLeaderboards()) { - if (getSecurityService().hasCurrentUserReadPermission(leaderboard)) { + if (securityService.hasCurrentUserReadPermission(leaderboard)) { for (Competitor competitor : leaderboard.getAllCompetitors()) { competitorIds.add(competitor.getId()); } @@ -111,21 +113,21 @@ public class MasterDataResource extends AbstractSailingServerResource { ArrayList events = new ArrayList<>(); for (Event event : getService().getAllEvents()) { - if (getSecurityService().hasCurrentUserReadPermission(event)) { + if (securityService.hasCurrentUserReadPermission(event)) { events.add(event); } } ArrayList mediaTracks = new ArrayList<>(); for (MediaTrack mediaTrack : getService().getAllMediaTracks()) { - if (getSecurityService().hasCurrentUserReadPermission(mediaTrack)) { + if (securityService.hasCurrentUserReadPermission(mediaTrack)) { mediaTracks.add(mediaTrack); } } Map regattaRaceIds = new HashMap<>(); for (Entry regattaRaceMap : getService().getPersistentRegattasForRaceIDs().entrySet()) { - if (getSecurityService().hasCurrentUserReadPermission(regattaRaceMap.getValue())) { + if (securityService.hasCurrentUserReadPermission(regattaRaceMap.getValue())) { regattaRaceIds.put(regattaRaceMap.getKey(), regattaRaceMap.getValue()); } } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyMasterDataRessource.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyMasterDataRessource.java new file mode 100644 index 00000000000..f0164713946 --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyMasterDataRessource.java @@ -0,0 +1,14 @@ +package com.sap.sailing.server.test; + +import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource; +import com.sap.sse.security.SecurityService; + +/** + * required for package visibility for getSecurityService + */ +public class DummyMasterDataRessource extends MasterDataResource { + @Override + protected SecurityService getSecurityService() { + return super.getSecurityService(); + } +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java new file mode 100644 index 00000000000..ae02dec4051 --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java @@ -0,0 +1,27 @@ +package com.sap.sailing.server.test; + +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.media.MediaDB; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sse.common.TypeBasedServiceFinderFactory; +import com.sap.sse.security.SecurityService; + +public class DummyRacingEventService extends RacingEventServiceImpl { + @Override + public SecurityService getSecurityService() { + return super.getSecurityService(); + } + + public DummyRacingEventService(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, + MediaDB mediaDB, WindStore windStore, SensorFixStore sensorFixStore, boolean restoreTrackedRaces) { + super(domainObjectFactory, mongoObjectFactory, mediaDB, windStore, sensorFixStore, restoreTrackedRaces); + } + + public DummyRacingEventService(WindStore windStore, SensorFixStore sensorFixStore, + TypeBasedServiceFinderFactory serviceFinderFactory) { + super(windStore, sensorFixStore, serviceFinderFactory); + } +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java new file mode 100644 index 00000000000..37e76791784 --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java @@ -0,0 +1,26 @@ +package com.sap.sailing.server.test; + +import com.sap.sailing.domain.common.impl.DataImportProgressImpl; +import com.sap.sailing.server.testsupport.RacingEventServiceImplMock; +import com.sap.sse.common.TypeBasedServiceFinderFactory; +import com.sap.sse.security.SecurityService; + +public class DummyRacingEventServiceImplMock extends RacingEventServiceImplMock { + @Override + public SecurityService getSecurityService() { + return super.getSecurityService(); + } + + public DummyRacingEventServiceImplMock() { + super(); + } + + public DummyRacingEventServiceImplMock(DataImportProgressImpl dataImportProgressImpl, + TypeBasedServiceFinderFactory serviceFinderFactory) { + super(dataImportProgressImpl, serviceFinderFactory); + } + + public DummyRacingEventServiceImplMock(DataImportProgressImpl dataImportProgressImpl) { + super(dataImportProgressImpl); + } +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index ec608e1cd38..71dbf4cd846 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -30,6 +30,9 @@ import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.subject.Subject; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -115,6 +118,8 @@ import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.PersistenceFactory; import com.sap.sailing.domain.persistence.media.MediaDBFactory; import com.sap.sailing.domain.racelog.tracking.EmptySensorFixStore; @@ -129,7 +134,6 @@ import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; -import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource; import com.sap.sailing.server.impl.RacingEventServiceImpl; import com.sap.sailing.server.masterdata.DummyTrackedRace; import com.sap.sailing.server.masterdata.MasterDataImporter; @@ -145,6 +149,9 @@ import com.sap.sse.common.media.MimeType; import com.sap.sse.mongodb.MongoDBConfiguration; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.security.SecurityService; +import com.sap.sse.security.shared.User; +import com.sap.sse.security.shared.WithQualifiedObjectIdentifier; +import com.sap.sse.security.shared.impl.UserGroupImpl; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.shared.media.VideoDescriptor; @@ -172,6 +179,8 @@ public class MasterDataImportTest { * Log Events created when running test. Will be removed from db at teardown */ private Set storedLogUUIDs = new HashSet(); + private DummyRacingEventService sourceService; + private DummyMasterDataRessource masterDataResource; @After public void tearDown() { @@ -180,7 +189,43 @@ public class MasterDataImportTest { @Before public void setUp() { + UserGroupImpl defaultTenant = new UserGroupImpl(new UUID(0, 1), "defaultTenant"); + User currentUser = Mockito.mock(User.class); + securityService = Mockito.mock(SecurityService.class); + SecurityManager securityManager = Mockito.mock(org.apache.shiro.mgt.SecurityManager.class); + Subject fakeSubject = Mockito.mock(Subject.class); + + SecurityUtils.setSecurityManager(securityManager); + Mockito.doReturn(fakeSubject).when(securityManager).createSubject(Mockito.any()); + Mockito.doReturn(defaultTenant).when(securityService).getDefaultTenant(); + Mockito.doReturn(currentUser).when(securityService).getCurrentUser(); + Mockito.doReturn(true).when(securityService).hasCurrentUserReadPermission(Mockito.any()); + Mockito.doNothing().when(securityService).checkCurrentUserReadPermission(Mockito.any()); + + Mockito.doReturn(true).when(securityService) + .hasCurrentUserReadPermission(Mockito.any(WithQualifiedObjectIdentifier.class)); + + + Mockito.doReturn(true).when(fakeSubject).isAuthenticated(); + + DomainFactory sourceDomainFactory = new DomainFactoryImpl((srlid) -> null); + + DomainObjectFactory dbFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE, + sourceDomainFactory); + MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE + .getMongoObjectFactory(MongoDBService.INSTANCE); + + + sourceService = Mockito.spy(new DummyRacingEventService(dbFactory, mongoObjectFactory, + MediaDBFactory.INSTANCE.getDefaultMediaDB(), EmptyWindStore.INSTANCE, EmptySensorFixStore.INSTANCE, + false)); + + + masterDataResource = spyResource(new DummyMasterDataRessource(), sourceService); + doReturn(securityService).when(masterDataResource).getSecurityService(); + doReturn(securityService).when(sourceService).getSecurityService(); + deleteAllDataFromDatabase(); } @@ -212,7 +257,10 @@ public class MasterDataImportTest { ClassNotFoundException { // Setup source service MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory(); - RacingEventService sourceService = new RacingEventServiceImpl(null, null, serviceFinderFactory); + DummyRacingEventService sourceService = Mockito + .spy(new DummyRacingEventService(null, null, serviceFinderFactory)); + Mockito.doReturn(securityService).when(sourceService).getSecurityService(); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -277,7 +325,8 @@ public class MasterDataImportTest { null, team2, /* timeOnTimeFactor */null, /* timeOnDistanceAllowancePerNauticalMile */null, null); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(raceId, createCompetitorsAndBoatsMap(boatClass, competitors), regatta, null, sourceService.getWindStore()); + TrackedRace trackedRace = new DummyTrackedRace(raceId, createCompetitorsAndBoatsMap(boatClass, competitors), + regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -352,10 +401,10 @@ public class MasterDataImportTest { List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); - RacingEventService destService; + DummyRacingEventServiceImplMock destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); + DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); + Mockito.doReturn(securityService).when(spyResource).getSecurityService(); Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -367,7 +416,11 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID), serviceFinderFactory){}; + destService = Mockito.spy( + new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID), serviceFinderFactory) { + }); + Mockito.doReturn(securityService).when(destService).getSecurityService(); + domainFactory = destService.getBaseDomainFactory(); DB db = destService.getMongoObjectFactory().getDatabase(); db.setWriteConcern(WriteConcern.SAFE); @@ -536,7 +589,6 @@ public class MasterDataImportTest { public void testMasterDataImportForScoreCorrections() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -554,7 +606,8 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, + emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -595,7 +648,8 @@ public class MasterDataImportTest { /* timeOnTimeFactor */null, /* timeOnDistanceAllowancePerNauticalMile */null, null); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(raceId, createCompetitorsAndBoatsMap(boatClass, competitors), regatta, null, sourceService.getWindStore()); + TrackedRace trackedRace = new DummyTrackedRace(raceId, createCompetitorsAndBoatsMap(boatClass, competitors), + regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -616,29 +670,26 @@ public class MasterDataImportTest { groupNamesToExport.add(group.getName()); RacingEventService destService; - DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); - ByteArrayInputStream inputStream = null; + DomainFactory domainFactory; try { streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); + domainFactory = destService.getBaseDomainFactory(); - inputStream = new ByteArrayInputStream(os.toByteArray()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService, securityService); importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -691,6 +742,14 @@ public class MasterDataImportTest { raceColumnOnTarget, MillisecondsTimePoint.now())); } + private RacingEventService getDestService(UUID randomUUID) { + DummyRacingEventServiceImplMock destService = Mockito + .spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + }); + Mockito.doReturn(securityService).when(destService).getSecurityService(); + return destService; + } + @Test public void testMasterDataImportForWind() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { @@ -762,10 +821,10 @@ public class MasterDataImportTest { List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); - RacingEventService destService; + DummyRacingEventServiceImplMock destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); + DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); + Mockito.doReturn(securityService).when(spyResource).getSecurityService(); Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -777,7 +836,9 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = Mockito.spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + }); + Mockito.doReturn(securityService).when(destService).getSecurityService(); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -848,15 +909,10 @@ public class MasterDataImportTest { } + @Test public void testMasterDataImportForRaceLogEventsReferencingCompetitors() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - DomainFactory sourceDomainFactory = new DomainFactoryImpl((srlid) -> null); - RacingEventService sourceService = new RacingEventServiceImpl( - PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE, sourceDomainFactory), - PersistenceFactory.INSTANCE.getMongoObjectFactory(MongoDBService.INSTANCE), - MediaDBFactory.INSTANCE.getDefaultMediaDB(), EmptyWindStore.INSTANCE, EmptySensorFixStore.INSTANCE, /* restoreTrackedRaces */ false); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -904,7 +960,7 @@ public class MasterDataImportTest { "Der Lennart halt"); DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); - Competitor competitor = sourceDomainFactory.getOrCreateCompetitor(competitorUUID, "Froderik", "F", Color.RED, + Competitor competitor = sourceService.getBaseDomainFactory().getOrCreateCompetitor(competitorUUID, "Froderik", "F", Color.RED, "noone@nowhere.de", null, team, /* timeOnTimeFactor */null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null); competitors.add(competitor); @@ -913,7 +969,7 @@ public class MasterDataImportTest { sailors2.add(new PersonImpl("Test Mustermann", new NationalityImpl("GER"), new Date(645487200000L), "desc")); DynamicPerson coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc"); DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2); - Competitor competitor2 = sourceDomainFactory.getCompetitorAndBoatStore().getOrCreateCompetitor(competitor2UUID, + Competitor competitor2 = sourceService.getBaseDomainFactory().getCompetitorAndBoatStore().getOrCreateCompetitor(competitor2UUID, "Froderik", "F", Color.RED, "noone@nowhere.de", null, team2, /* timeOnTimeFactor */null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null); competitors.add(competitor2); @@ -946,9 +1002,7 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); @@ -959,7 +1013,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -1109,9 +1163,7 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); @@ -1127,7 +1179,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); // Create existing data on target venueNameNotToOverride = "doNotOverride"; @@ -1170,7 +1222,6 @@ public class MasterDataImportTest { importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -1215,8 +1266,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportWithOverrideWithoutHttpStack() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -1311,21 +1360,18 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); RegattaAndRaceIdentifier identifierOfRegattaTrackedRace; - ByteArrayInputStream inputStream = null; try { streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); // Create existing data on target String venueNameToOverride = "Override"; @@ -1383,13 +1429,12 @@ public class MasterDataImportTest { null, false, new ArrayList(), null, null); destService.getLeaderboardGroupByName(TEST_GROUP_NAME).addLeaderboard(leaderboardToOverride); destService.addLeaderboard(leaderboardToOverride); - inputStream = new ByteArrayInputStream(os.toByteArray()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService, securityService); importer.importFromStream(inputStream, randomUUID, true); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -1445,8 +1490,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportForRegattaDefaultProcedureAndDesigner() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */null, new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), /* officialWebsiteURL */null, /*baseURL*/null, @@ -1478,9 +1521,7 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); @@ -1491,7 +1532,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -1513,9 +1554,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportForRegattaWithoutCourseArea() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */null, new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), /* officialWebsiteURL */null, /*baseURL*/null, @@ -1612,28 +1650,24 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); - ByteArrayInputStream inputStream = null; try { streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); - inputStream = new ByteArrayInputStream(os.toByteArray()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService, securityService); importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -1650,8 +1684,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportForPersistentRegattaRaceIDsWithoutHttpStack() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -1749,28 +1781,24 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); - ByteArrayInputStream inputStream = null; try { streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); - inputStream = new ByteArrayInputStream(os.toByteArray()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService, securityService); importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -1846,10 +1874,10 @@ public class MasterDataImportTest { // Serialize List groupNamesToExport = Collections.singletonList(leaderboardGroup.getName()); - RacingEventService destService; + DummyRacingEventServiceImplMock destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); + DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); + Mockito.doReturn(securityService).when(spyResource).getSecurityService(); Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -1861,7 +1889,9 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = Mockito.spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + }); + Mockito.doReturn(securityService).when(destService).getSecurityService(); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -1895,7 +1925,6 @@ public class MasterDataImportTest { public void testMasterDataImportWithTwoLgsWithSameLeaderboard() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); @@ -1972,9 +2001,7 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); @@ -1985,7 +2012,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -2020,9 +2047,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportWithOverallLeaderboard() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); - int[] discardRule = { 1, 2, 3, 4 }; ScoringScheme scheme = new LowPoint(); List leaderboardNames = new ArrayList(); @@ -2044,28 +2068,24 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); - ByteArrayInputStream inputStream = null; try { streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); - inputStream = new ByteArrayInputStream(os.toByteArray()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService, securityService); importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) @@ -2124,8 +2144,6 @@ public class MasterDataImportTest { @Test public void testMasterDataImportWithFlexibleLeaderboard() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { - // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); UUID courseAreaUUID = UUID.randomUUID(); List raceColumnNames = new ArrayList(); @@ -2230,9 +2248,7 @@ public class MasterDataImportTest { RacingEventService destService; DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); - Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); + Response response = masterDataResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); @@ -2243,7 +2259,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)){}; + destService = getDestService(randomUUID); domainFactory = destService.getBaseDomainFactory(); inputStream = new ByteArrayInputStream(os.toByteArray()); From 12df19092f521f16ac742f77d5f998d3a2574b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 15:40:36 +0100 Subject: [PATCH 28/42] fixed and simplified further tests --- .../test/DummyMasterDataResource.java | 11 ++++++ .../test/MediaReplicationTest.java | 36 +++++++++++++------ .../server/test/DummyRacingEventService.java | 27 -------------- .../test/DummyRacingEventServiceImplMock.java | 26 -------------- .../server/test/MasterDataImportTest.java | 25 ++++++------- 5 files changed, 50 insertions(+), 75 deletions(-) create mode 100644 java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DummyMasterDataResource.java delete mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java delete mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DummyMasterDataResource.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DummyMasterDataResource.java new file mode 100644 index 00000000000..eec612be422 --- /dev/null +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DummyMasterDataResource.java @@ -0,0 +1,11 @@ +package com.sap.sailing.server.replication.test; + +import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource; +import com.sap.sse.security.SecurityService; + +public class DummyMasterDataResource extends MasterDataResource { + @Override + protected SecurityService getSecurityService() { + return super.getSecurityService(); + } +} diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/MediaReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/MediaReplicationTest.java index a3cad59a23d..8bdcab738f7 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/MediaReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/MediaReplicationTest.java @@ -37,7 +37,6 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.test.TrackBasedTest; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; -import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource; import com.sap.sailing.server.impl.RacingEventServiceImpl; import com.sap.sailing.server.masterdata.MasterDataImporter; import com.sap.sse.common.Duration; @@ -49,6 +48,9 @@ import com.sap.sse.common.media.MimeType; import com.sap.sse.mongodb.MongoDBConfiguration; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.security.SecurityService; +import com.sap.sse.security.shared.User; +import com.sap.sse.security.shared.WithQualifiedObjectIdentifier; +import com.sap.sse.security.shared.impl.UserGroupImpl; import junit.framework.Assert; @@ -188,8 +190,22 @@ public class MediaReplicationTest extends AbstractServerReplicationTest { @Test public void testMasterDataImportForMediaTracks() throws MalformedURLException, IOException, InterruptedException, ClassNotFoundException { + + UserGroupImpl defaultTenant = new UserGroupImpl(new UUID(0, 1), "defaultTenant"); + User currentUser = Mockito.mock(User.class); + + SecurityService securityService = Mockito.mock(SecurityService.class); + Mockito.doReturn(defaultTenant).when(securityService).getDefaultTenant(); + Mockito.doReturn(currentUser).when(securityService).getCurrentUser(); + Mockito.doReturn(true).when(securityService).hasCurrentUserReadPermission(Mockito.any()); + Mockito.doNothing().when(securityService).checkCurrentUserReadPermission(Mockito.any()); + Mockito.doReturn(true).when(securityService) + .hasCurrentUserReadPermission(Mockito.any(WithQualifiedObjectIdentifier.class)); + + // Setup source service - RacingEventService sourceService = new RacingEventServiceImpl(); + RacingEventServiceImpl sourceService = Mockito.spy(new RacingEventServiceImpl()); + Mockito.doReturn(securityService).when(sourceService).getSecurityService(); Set assignedRaces = new HashSet(); String regattaName1 = "49er"; String regattaName2 = "49er FX"; @@ -234,32 +250,32 @@ public class MediaReplicationTest extends AbstractServerReplicationTest { List groupNamesToExport = Collections.singletonList(leaderboardGroup.getName()); final DomainFactory domainFactory; - MasterDataResource resource = new MasterDataResource(); - MasterDataResource spyResource = spyResource(resource, sourceService); + DummyMasterDataResource spyResource = spyResource(new DummyMasterDataResource(), sourceService); + Mockito.doReturn(securityService).when(spyResource).getSecurityService(); Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true, false); StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); ByteArrayOutputStream os = new ByteArrayOutputStream(); UUID randomUUID = UUID.randomUUID(); - ByteArrayInputStream inputStream = null; try { + RacingEventServiceImpl fmaster = Mockito.spy(master); + Mockito.doReturn(securityService).when(fmaster).getSecurityService(); streamingOutput.write(os); os.flush(); // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - domainFactory = master.getBaseDomainFactory(); + domainFactory = fmaster.getBaseDomainFactory(); // ensure that this class's class loader and with it the dependency to com.sap.sailing.domain.test // is known during de-serialization because anonymous inner classes from that bundle may be used // in the object graph, e.g., for RankingMetricConstructor objects based on locally-instantiated lambda // expressions - master.addMasterDataClassLoader(this.getClass().getClassLoader()); - inputStream = new ByteArrayInputStream(os.toByteArray()); - MasterDataImporter importer = new MasterDataImporter(domainFactory, master, + fmaster.addMasterDataClassLoader(this.getClass().getClassLoader()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray()); + MasterDataImporter importer = new MasterDataImporter(domainFactory, fmaster, Mockito.mock(SecurityService.class)); importer.importFromStream(inputStream, randomUUID, false); } finally { os.close(); - inputStream.close(); } // ---Asserts--- diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java deleted file mode 100644 index ae02dec4051..00000000000 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.sap.sailing.server.test; - -import com.sap.sailing.domain.persistence.DomainObjectFactory; -import com.sap.sailing.domain.persistence.MongoObjectFactory; -import com.sap.sailing.domain.persistence.media.MediaDB; -import com.sap.sailing.domain.racelog.tracking.SensorFixStore; -import com.sap.sailing.domain.tracking.WindStore; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sse.common.TypeBasedServiceFinderFactory; -import com.sap.sse.security.SecurityService; - -public class DummyRacingEventService extends RacingEventServiceImpl { - @Override - public SecurityService getSecurityService() { - return super.getSecurityService(); - } - - public DummyRacingEventService(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, - MediaDB mediaDB, WindStore windStore, SensorFixStore sensorFixStore, boolean restoreTrackedRaces) { - super(domainObjectFactory, mongoObjectFactory, mediaDB, windStore, sensorFixStore, restoreTrackedRaces); - } - - public DummyRacingEventService(WindStore windStore, SensorFixStore sensorFixStore, - TypeBasedServiceFinderFactory serviceFinderFactory) { - super(windStore, sensorFixStore, serviceFinderFactory); - } -} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java deleted file mode 100644 index 37e76791784..00000000000 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/DummyRacingEventServiceImplMock.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.server.test; - -import com.sap.sailing.domain.common.impl.DataImportProgressImpl; -import com.sap.sailing.server.testsupport.RacingEventServiceImplMock; -import com.sap.sse.common.TypeBasedServiceFinderFactory; -import com.sap.sse.security.SecurityService; - -public class DummyRacingEventServiceImplMock extends RacingEventServiceImplMock { - @Override - public SecurityService getSecurityService() { - return super.getSecurityService(); - } - - public DummyRacingEventServiceImplMock() { - super(); - } - - public DummyRacingEventServiceImplMock(DataImportProgressImpl dataImportProgressImpl, - TypeBasedServiceFinderFactory serviceFinderFactory) { - super(dataImportProgressImpl, serviceFinderFactory); - } - - public DummyRacingEventServiceImplMock(DataImportProgressImpl dataImportProgressImpl) { - super(dataImportProgressImpl); - } -} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 71dbf4cd846..d5f391e562b 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -179,7 +179,7 @@ public class MasterDataImportTest { * Log Events created when running test. Will be removed from db at teardown */ private Set storedLogUUIDs = new HashSet(); - private DummyRacingEventService sourceService; + private RacingEventService sourceService; private DummyMasterDataRessource masterDataResource; @After @@ -217,7 +217,8 @@ public class MasterDataImportTest { .getMongoObjectFactory(MongoDBService.INSTANCE); - sourceService = Mockito.spy(new DummyRacingEventService(dbFactory, mongoObjectFactory, + sourceService = Mockito + .spy(new RacingEventServiceImpl(dbFactory, mongoObjectFactory, MediaDBFactory.INSTANCE.getDefaultMediaDB(), EmptyWindStore.INSTANCE, EmptySensorFixStore.INSTANCE, false)); @@ -257,8 +258,8 @@ public class MasterDataImportTest { ClassNotFoundException { // Setup source service MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory(); - DummyRacingEventService sourceService = Mockito - .spy(new DummyRacingEventService(null, null, serviceFinderFactory)); + RacingEventServiceImpl sourceService = Mockito + .spy(new RacingEventServiceImpl(null, null, serviceFinderFactory)); Mockito.doReturn(securityService).when(sourceService).getSecurityService(); Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */null, eventStartDate, eventEndDate, @@ -401,7 +402,7 @@ public class MasterDataImportTest { List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); - DummyRacingEventServiceImplMock destService; + RacingEventServiceImplMock destService; DomainFactory domainFactory; DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); Mockito.doReturn(securityService).when(spyResource).getSecurityService(); @@ -417,7 +418,7 @@ public class MasterDataImportTest { deleteAllDataFromDatabase(); // Import in new service destService = Mockito.spy( - new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID), serviceFinderFactory) { + new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID), serviceFinderFactory) { }); Mockito.doReturn(securityService).when(destService).getSecurityService(); @@ -743,8 +744,8 @@ public class MasterDataImportTest { } private RacingEventService getDestService(UUID randomUUID) { - DummyRacingEventServiceImplMock destService = Mockito - .spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + RacingEventServiceImplMock destService = Mockito + .spy(new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { }); Mockito.doReturn(securityService).when(destService).getSecurityService(); return destService; @@ -821,7 +822,7 @@ public class MasterDataImportTest { List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); - DummyRacingEventServiceImplMock destService; + RacingEventServiceImplMock destService; DomainFactory domainFactory; DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); Mockito.doReturn(securityService).when(spyResource).getSecurityService(); @@ -836,7 +837,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = Mockito.spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + destService = Mockito.spy(new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { }); Mockito.doReturn(securityService).when(destService).getSecurityService(); domainFactory = destService.getBaseDomainFactory(); @@ -1874,7 +1875,7 @@ public class MasterDataImportTest { // Serialize List groupNamesToExport = Collections.singletonList(leaderboardGroup.getName()); - DummyRacingEventServiceImplMock destService; + RacingEventServiceImplMock destService; DomainFactory domainFactory; DummyMasterDataRessource spyResource = spyResource(new DummyMasterDataRessource(), sourceService); Mockito.doReturn(securityService).when(spyResource).getSecurityService(); @@ -1889,7 +1890,7 @@ public class MasterDataImportTest { // Delete all data above from the database, to allow recreating all of it on target server deleteAllDataFromDatabase(); // Import in new service - destService = Mockito.spy(new DummyRacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { + destService = Mockito.spy(new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)) { }); Mockito.doReturn(securityService).when(destService).getSecurityService(); domainFactory = destService.getBaseDomainFactory(); From 1bf769775246ecd7ad7e3b33fafe554695687d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 15:58:52 +0100 Subject: [PATCH 29/42] simplified taggingService, as the RacingEventService already has a SecurityService --- .../sailing/server/tagging/TaggingServiceImpl.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java index 8f0b77de844..7e653919b58 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java @@ -5,7 +5,6 @@ import java.util.List; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; -import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent; @@ -23,23 +22,19 @@ import com.sap.sailing.domain.common.tagging.TagAlreadyExistsException; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.impl.Activator; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.security.SecurityService; -import com.sap.sse.util.ServiceTrackerFactory; public class TaggingServiceImpl implements TaggingService { - private final ServiceTracker securityServiceTracker; private final RacingEventService racingService; private final TagDTODeSerializer serializer; public TaggingServiceImpl(RacingEventService racingService) { this.racingService = racingService; serializer = new TagDTODeSerializer(); - securityServiceTracker = ServiceTrackerFactory.createAndOpen(Activator.getContext(), SecurityService.class); } /** @@ -62,14 +57,7 @@ public class TaggingServiceImpl implements TaggingService { * @return instance of {@link SecurityService} */ private SecurityService getSecurityService() { - SecurityService securityService = null; - if (securityServiceTracker != null) { - securityService = securityServiceTracker.getService(); - } - if (securityService == null) { - throw new RuntimeException("Security service not found!"); - } - return securityService; + return racingService.getSecurityService(); } private void addPublicTag(String leaderboardName, String raceColumnName, String fleetName, String tag, From 140b9abafc12785cd08e0f95cd89893b16604251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 15:59:24 +0100 Subject: [PATCH 30/42] fixed search and tagging tests --- .../server/test/SearchServiceTest.java | 33 ++++++++++++++++++- .../server/test/TaggingServiceTest.java | 6 +++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index d2f20595077..200c7e0c548 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -17,8 +17,12 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.subject.Subject; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import com.sap.sailing.domain.base.CompetitorWithBoat; import com.sap.sailing.domain.base.DomainFactory; @@ -70,12 +74,17 @@ import com.sap.sailing.server.operationaltransformation.RemoveEvent; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.RemoveRegatta; +import com.sap.sailing.server.tagging.TaggingServiceImpl; import com.sap.sse.common.Color; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; +import com.sap.sse.security.SecurityService; +import com.sap.sse.security.shared.User; +import com.sap.sse.security.shared.WithQualifiedObjectIdentifier; +import com.sap.sse.security.shared.impl.UserGroupImpl; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.shared.media.VideoDescriptor; @@ -100,10 +109,32 @@ public class SearchServiceTest { private DynamicTrackedRace pfingstbusch470TrackedR2; private DynamicTrackedRace aalOrcTrackedR1; private DynamicTrackedRace aalOrcTrackedR2; + private SecurityService securityService; @Before public void setUp() { - server = new RacingEventServiceImpl(); + UserGroupImpl defaultTenant = new UserGroupImpl(new UUID(0, 1), "defaultTenant"); + User currentUser = Mockito.mock(User.class); + + securityService = Mockito.mock(SecurityService.class); + SecurityManager securityManager = Mockito.mock(org.apache.shiro.mgt.SecurityManager.class); + Subject fakeSubject = Mockito.mock(Subject.class); + + SecurityUtils.setSecurityManager(securityManager); + Mockito.doReturn(fakeSubject).when(securityManager).createSubject(Mockito.any()); + Mockito.doReturn(defaultTenant).when(securityService).getDefaultTenant(); + Mockito.doReturn(currentUser).when(securityService).getCurrentUser(); + Mockito.doReturn(true).when(securityService).hasCurrentUserReadPermission(Mockito.any()); + Mockito.doNothing().when(securityService).checkCurrentUserReadPermission(Mockito.any()); + + Mockito.doReturn(true).when(securityService) + .hasCurrentUserReadPermission(Mockito.any(WithQualifiedObjectIdentifier.class)); + Mockito.doReturn(true).when(fakeSubject).isAuthenticated(); + + server = Mockito.spy(new RacingEventServiceImpl()); + Mockito.doReturn(securityService).when(server).getSecurityService(); + TaggingServiceImpl taggingServer = Mockito.spy(new TaggingServiceImpl(server)); + Mockito.doReturn(taggingServer).when(server).getTaggingService(); List allEvents = new ArrayList<>(); Util.addAll(server.getAllEvents(), allEvents); for (final Event e : allEvents) { diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java index 599aa5f5643..17c174cdd41 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java @@ -18,6 +18,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mockito; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -91,10 +92,13 @@ public class TaggingServiceTest { securityService = new SecurityBundleTestWrapper().initializeSecurityServiceForTesting(); // create & login user securityService.createSimpleUser(username, email, password, fullName, company, Locale.ENGLISH, null); + subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username, password)); // setup tagging service - taggingService = new TaggingServiceImpl(racingService); + taggingService = Mockito.spy(new TaggingServiceImpl(racingService)); + + Mockito.doReturn(taggingService).when(racingService).getTaggingService(); } @AfterClass From 83a0c2c32c21a21ea77cda238af7f033b78a22ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 16:04:00 +0100 Subject: [PATCH 31/42] fix for getDefaultTenantForUser using getCurrentUser --- .../com/sap/sse/security/impl/SecurityServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 0d82abe2780..1a2a64fe55c 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 @@ -366,10 +366,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat } public UserGroup getDefaultTenantForUser(User user) { - if (getCurrentUser() == null) { - return null; - } - UserGroup specificTenant = getCurrentUser().getDefaultTenant(ServerInfo.getName()); + UserGroup specificTenant = user.getDefaultTenant(ServerInfo.getName()); if (specificTenant == null) { String defaultTenantName = getDefaultTenantNameForUsername(user.getName()); specificTenant = getUserGroupByName(defaultTenantName); @@ -379,6 +376,9 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat @Override public UserGroup getDefaultTenantForCurrentUser() { + if (getCurrentUser() == null) { + return null; + } return getDefaultTenantForUser(getCurrentUser()); } From 6204f410a675b3265ee032e5b7af3304b44db538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 16:43:00 +0100 Subject: [PATCH 32/42] fix for SecurityManager exception and for TaggingServiceTest --- .../sailing/server/test/TaggingServiceTest.java | 10 +++------- .../sap/sse/security/impl/SecurityServiceImpl.java | 14 +++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java index 17c174cdd41..7f2e8c12dfd 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/TaggingServiceTest.java @@ -81,7 +81,7 @@ public class TaggingServiceTest { throws MalformedURLException, IOException, InterruptedException, UserManagementException, MailException, UserGroupManagementException { MongoDBService.INSTANCE.getDB().dropDatabase(); // setup racing service and racelog - racingService = new RacingEventServiceImpl(); + racingService = Mockito.spy(new RacingEventServiceImpl()); RacingEventServiceOperation addLeaderboardOp = new CreateFlexibleLeaderboard( leaderboardName, leaderboardName, new int[] { 5 }, new LowPoint(), null); racingService.apply(addLeaderboardOp); @@ -98,6 +98,7 @@ public class TaggingServiceTest { // setup tagging service taggingService = Mockito.spy(new TaggingServiceImpl(racingService)); + Mockito.doReturn(securityService).when(racingService).getSecurityService(); Mockito.doReturn(taggingService).when(racingService).getTaggingService(); } @@ -218,13 +219,12 @@ public class TaggingServiceTest { } @Test - public void testGetTags() { + public void testGetTags() throws Exception { logger.entering(getClass().getName(), "testGetTags"); final String tag = "TagToLoad"; final String comment = "Comment To Load"; final String imageURL = "localhost"; final TimePoint raceTimepoint = new MillisecondsTimePoint(1000); - try { logger.info("Adding tags which should be loaded via getTags() afterwards."); taggingService.addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL, imageURL, false, raceTimepoint); @@ -241,10 +241,6 @@ public class TaggingServiceTest { assertEquals("Public tags do not contain added tag with non-matching creation date filter", 0, taggingService.getPublicTags(leaderboardName, raceColumnName, fleetName, MillisecondsTimePoint.now(), false).size()); - } catch (Exception e) { - fail("Caught unexpected exception while loading tags which were previously added! " + e.getClass().getName() - + ", message: " + e.getMessage()); - } logger.exiting(getClass().getName(), "testGetTags"); } 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 1a2a64fe55c..3e6dc55f8d0 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 @@ -263,7 +263,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat logger.info("No users found, creating default user \""+ADMIN_USERNAME+"\" with password \""+ADMIN_DEFAULT_PASSWORD+"\""); final User adminUser = createSimpleUser(ADMIN_USERNAME, "nobody@sapsailing.com", ADMIN_DEFAULT_PASSWORD, - /* fullName */ null, /* company */ null, Locale.ENGLISH, /* validationBaseURL */ null); + /* fullName */ null, /* company */ null, Locale.ENGLISH, /* validationBaseURL */ null, + null); apply(s -> s.internalSetOwnership( SecuredSecurityTypes.USER.getQualifiedObjectIdentifier(ADMIN_USERNAME), ADMIN_USERNAME, null, @@ -376,7 +377,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat @Override public UserGroup getDefaultTenantForCurrentUser() { - if (getCurrentUser() == null) { + if (SecurityUtils.getSecurityManager() != null && getCurrentUser() == null) { return null; } return getDefaultTenantForUser(getCurrentUser()); @@ -690,6 +691,13 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat public UserImpl createSimpleUser(final String username, final String email, String password, String fullName, String company, Locale locale, final String validationBaseURL) throws UserManagementException, MailException, UserGroupManagementException { + return createSimpleUser(username, email, password, fullName, company, locale, validationBaseURL, + getDefaultTenantForCurrentUser()); + } + + private UserImpl createSimpleUser(final String username, final String email, String password, String fullName, + String company, Locale locale, final String validationBaseURL, UserGroup userOwner) + throws UserManagementException, MailException, UserGroupManagementException { logger.info("Creating user "+username); if (userStore.getUserByName(username) != null) { logger.warning("User "+username+" already exists"); @@ -720,7 +728,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat // the new user becomes its owner to ensure the user role is correctly working // the default tenant is the owning tenant to allow users having admin role for a specific server tenant to also be able to delete users accessControlStore.setOwnership(SecuredSecurityTypes.USER.getQualifiedObjectIdentifier(username), result, - getDefaultTenantForCurrentUser(), username); + userOwner, username); // the new user becomes the owning user of its own specific tenant which initially only contains the new user accessControlStore.setOwnership(SecuredSecurityTypes.USER_GROUP.getQualifiedObjectIdentifier(tenant.getId().toString()), result, tenant, tenant.getName()); From 8564c2a24b1c86964bd2afd1e7682cc26b15f91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Tue, 13 Nov 2018 17:22:02 +0100 Subject: [PATCH 33/42] fixed test argument mismatch, and made more resistant to future changes. --- .../server/gateway/test/jaxrs/AbstractJaxRsApiTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 79f3fe1f288..ea3fb8cec33 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 @@ -81,9 +81,15 @@ public abstract class AbstractJaxRsApiTest { }).when(securityService).setOwnershipCheckPermissionForObjectCreationAndRevertOnError( Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(Action.class)); Mockito.doAnswer(new Answer() { + @SuppressWarnings("rawtypes") @Override public Object answer(InvocationOnMock invocation) throws Throwable { - return invocation.getArgumentAt(4, ActionWithResult.class).run(); + for (Object arg : invocation.getArguments()) { + if (arg instanceof ActionWithResult) { + return ((ActionWithResult) arg).run(); + } + } + return null; } }).when(securityService).setOwnershipCheckPermissionForObjectCreationAndRevertOnError( Mockito.any(), Mockito.any(), Mockito.any(), From fc0aaf0110bc507de3b207bd42e8a01e3f389b35 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 13 Nov 2018 17:47:23 +0100 Subject: [PATCH 34/42] fixed bug bug4800 by adding a "pointer" cursor for tag cell items and using a cell preview handler handling the click event Change-Id: I55f1ac25970b05f1c20232f2864d6dbe5db04cf0 --- .../ui/raceboard/tagging/TaggingPanel.java | 53 +++++++++++-------- .../ui/raceboard/tagging/tagging-panel.gss | 1 + 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java index 472597bc683..5b0c3585b0d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.user.cellview.client.CellList; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -16,6 +17,8 @@ import com.google.gwt.user.client.ui.HeaderPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.CellPreviewEvent; +import com.google.gwt.view.client.CellPreviewEvent.Handler; import com.google.gwt.view.client.NoSelectionModel; import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -116,8 +119,8 @@ public class TaggingPanel extends ComponentWithoutSettings * 3) another user adds/deletes/changes any tag between the latest received tag and the current timer position
* consecutively, the timer would jump to this new tag as the selection would change automatically as the latest tag * changed. This selection change would also trigger the timer to jump to the latest tag, which is not intended in - * this case. Therefor any received changes on any tags will set this boolen to true which will ignore the time jump - * at the selection change event and prevent this wrong behaviour. + * this case. Therefor any received changes on any tags will set this boolean to true which will ignore the time jump + * at the selection change event and prevent this wrong behavior. * * @see #raceTimesInfosReceived(Map, long, Date, long) */ @@ -181,26 +184,31 @@ public class TaggingPanel extends ComponentWithoutSettings tagCellList.setEmptyListWidget(new Label(stringMessages.tagNoTagsFound())); tagCellList.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED); tagCellList.setSelectionModel(tagSelectionModel); - tagSelectionModel.addSelectionChangeHandler(event -> { - // set time slider to corresponding position - TagDTO selectedTag = tagSelectionModel.getSelectedObject(); - if (selectedTag != null) { - /** - * Do not set time of timer when {@link #preventTimeJumpAtSelectionChangeForOnce} is set to - * true. In this case set {@link #preventTimeJumpAtSelectionChangeForOnce} to - * false as selection change is ignored once. - * - * @see #preventTimeJumpAtSelectionChangeForOnce - */ - if (preventTimeJumpAtSelectionChangeForOnce) { - preventTimeJumpAtSelectionChangeForOnce = false; - } else { - // remove time change listener when manual selecting tag cells as this could end in an infinite loop - // of timer change -> automatic selection change -> timer change -> ... - timer.removeTimeListener(this); - timer.setTime(selectedTag.getRaceTimepoint().asMillis()); - // adding time change listener again - timer.addTimeListener(this); + tagCellList.addCellPreviewHandler(new Handler() { + @Override + public void onCellPreview(CellPreviewEvent event) { + if (BrowserEvents.CLICK.equals(event.getNativeEvent().getType())) { + // set time slider to corresponding position + TagDTO selectedTag = event.getValue(); + if (selectedTag != null) { + /** + * Do not set time of timer when {@link #preventTimeJumpAtSelectionChangeForOnce} is set to + * true. In this case set {@link #preventTimeJumpAtSelectionChangeForOnce} to + * false as selection change is ignored once. + * + * @see #preventTimeJumpAtSelectionChangeForOnce + */ + if (preventTimeJumpAtSelectionChangeForOnce) { + preventTimeJumpAtSelectionChangeForOnce = false; + } else { + // remove time change listener when manually selecting tag cells as this could end in an infinite loop + // of timer change -> automatic selection change -> timer change -> ... + timer.removeTimeListener(TaggingPanel.this); + timer.setTime(selectedTag.getRaceTimepoint().asMillis()); + // adding time change listener again + timer.addTimeListener(TaggingPanel.this); + } + } } } }); @@ -210,7 +218,6 @@ public class TaggingPanel extends ComponentWithoutSettings createTagsButton.addClickHandler(event -> { setCurrentState(State.CREATE_TAG); }); - taggingPanel.setContentWidget(contentPanel); updateContent(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss index 173c73f3acb..e31a469b16e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss @@ -87,6 +87,7 @@ padding: 5px 5px 0; border: 1px solid lightgrey; border-radius: 3px; + cursor: pointer; } .tagCellHeading { From 3b142863d6f879386e31e80bce09f6b9c71043bc Mon Sep 17 00:00:00 2001 From: Benjamin Barth Date: Tue, 13 Nov 2018 17:53:55 +0100 Subject: [PATCH 35/42] Some more styling improvements to the "what's new" dialog --- .../sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss index 11ee2514bea..b2fc68c17e9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/dialog/Dialog.gss @@ -2,7 +2,7 @@ left: 50% !important; top: 50% !important; position: absolute !important; - background-color: #f2f2f2; + background-color: #ffffff; padding: 1em; border: 1px solid #ccc; border-radius: 3px; @@ -38,6 +38,7 @@ .buttonPanel { text-align: center; + font-size: 0; } .backgroundPanel { @@ -64,5 +65,6 @@ .buttonAdjustments { min-width: 100%; + margin: .5em 0 0; } } From 61e0f6aa7c3aa0dbbf670267d053c2d0e6daa596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Wed, 14 Nov 2018 09:34:38 +0100 Subject: [PATCH 36/42] fixed multiple points interacting with the userstore directly, that might cause issues with replication --- .../home/server/SailingDispatchContextImpl.java | 7 ++----- .../server/servlets/SailingDispatchServlet.java | 5 +---- .../gwt/ui/server/DataMiningServiceImpl.java | 6 +----- .../server/StoredDataMiningQueryPersisterImpl.java | 11 +++-------- .../gateway/jaxrs/api/PreferencesResource.java | 14 +++++++------- .../src/com/sap/sse/security/SecurityService.java | 7 ++++++- .../sap/sse/security/impl/SecurityServiceImpl.java | 6 ++++++ 7 files changed, 26 insertions(+), 30 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java index 956205892d8..a20b527303c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java @@ -19,7 +19,6 @@ import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.shared.exceptions.ServerDispatchException; import com.sap.sse.security.SecurityService; import com.sap.sse.security.User; -import com.sap.sse.security.UserStore; @GwtIncompatible public class SailingDispatchContextImpl implements SailingDispatchContext { @@ -30,20 +29,18 @@ public class SailingDispatchContextImpl implements SailingDispatchContext { private String clientLocaleName; private final HttpServletRequest request; private final SecurityService securityService; - private final UserStore userStore; private final TrackedRaceStatisticsCache trackedRaceStatisticsCache; private final WindFinderTrackerFactory windFinderTrackerFactory; public SailingDispatchContextImpl(Date currentClientTime, RacingEventService racingEventService, WindFinderTrackerFactory windFinderTrackerFactory, EventNewsService eventNewsService, - SecurityService securityService, UserStore userStore, TrackedRaceStatisticsCache trackedRaceStatisticsCache, + SecurityService securityService, TrackedRaceStatisticsCache trackedRaceStatisticsCache, String clientLocaleName, HttpServletRequest request) { this.currentClientTime = currentClientTime; this.racingEventService = racingEventService; this.windFinderTrackerFactory = windFinderTrackerFactory; this.eventNewsService = eventNewsService; this.securityService = securityService; - this.userStore = userStore; this.trackedRaceStatisticsCache = trackedRaceStatisticsCache; this.clientLocaleName = clientLocaleName; this.request = request; @@ -96,7 +93,7 @@ public class SailingDispatchContextImpl implements SailingDispatchContext { public T getPreferenceForCurrentUser(String preferenceKey) { User currentUser = securityService.getCurrentUser(); if (currentUser != null) { - return userStore.getPreferenceObject(currentUser.getName(), preferenceKey); + return securityService.getPreferenceObject(currentUser.getName(), preferenceKey); } return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java index 14a5070e01e..fa49c0b7cf6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java @@ -24,7 +24,6 @@ import com.sap.sse.gwt.dispatch.servlets.AbstractDispatchServlet; import com.sap.sse.gwt.dispatch.shared.commands.Action; import com.sap.sse.gwt.dispatch.shared.commands.Result; import com.sap.sse.security.SecurityService; -import com.sap.sse.security.UserStore; import com.sap.sse.util.ServiceTrackerFactory; public class SailingDispatchServlet extends AbstractDispatchServlet { @@ -34,7 +33,6 @@ public class SailingDispatchServlet extends AbstractDispatchServlet windFinderTrackerFactory; private final ServiceTracker eventNewsServiceTracker; private final ServiceTracker securityServiceTracker; - private final ServiceTracker userStoreTracker; private final ServiceTracker trackedRaceStatisticsCacheTracker; public SailingDispatchServlet() { @@ -43,7 +41,6 @@ public class SailingDispatchServlet extends AbstractDispatchServlet dataMiningServerTracker; private final ServiceTracker securityServiceTracker; - private final ServiceTracker userStoreServiceTracker; private final StoredDataMiningQueryPersister storedDataMiningQueryPersistor; @@ -74,10 +72,8 @@ public class DataMiningServiceImpl extends RemoteServiceServlet implements DataM context = Activator.getDefault(); dataMiningServerTracker = createAndOpenDataMiningServerTracker(context); securityServiceTracker = ServiceTrackerFactory.createAndOpen(context, SecurityService.class); - userStoreServiceTracker = ServiceTrackerFactory.createAndOpen(context, UserStore.class); - storedDataMiningQueryPersistor = new StoredDataMiningQueryPersisterImpl(securityServiceTracker.getService(), - userStoreServiceTracker.getService()); + storedDataMiningQueryPersistor = new StoredDataMiningQueryPersisterImpl(securityServiceTracker.getService()); dtoFactory = new DataMiningDTOFactory(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/StoredDataMiningQueryPersisterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/StoredDataMiningQueryPersisterImpl.java index eb15d0133e7..1385105766b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/StoredDataMiningQueryPersisterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/StoredDataMiningQueryPersisterImpl.java @@ -18,18 +18,14 @@ import com.sap.sse.datamining.shared.impl.dto.StoredDataMiningQueryDTOImpl; import com.sap.sse.gwt.dispatch.shared.exceptions.ServerDispatchException; import com.sap.sse.security.SecurityService; import com.sap.sse.security.User; -import com.sap.sse.security.UserStore; /** Implementation of {@link StoredDataMiningQueryPersister}. */ public class StoredDataMiningQueryPersisterImpl implements StoredDataMiningQueryPersister { private final SecurityService securityService; - private final UserStore userStore; - public StoredDataMiningQueryPersisterImpl(SecurityService securityService, UserStore userStore) { + public StoredDataMiningQueryPersisterImpl(SecurityService securityService) { this.securityService = securityService; - this.userStore = userStore; - } /** @return all {@link StoredDataMiningQueryDTO}s the user has stored in his user store. */ @@ -101,7 +97,7 @@ public class StoredDataMiningQueryPersisterImpl implements StoredDataMiningQuery User currentUser = securityService.getCurrentUser(); if (currentUser != null) { try { - userStore.setPreferenceObject(currentUser.getName(), preferenceKey, preference); + securityService.setPreferenceObject(currentUser.getName(), preferenceKey, preference); } catch (AuthorizationException e) { throw new ServerDispatchException(e); } @@ -124,9 +120,8 @@ public class StoredDataMiningQueryPersisterImpl implements StoredDataMiningQuery private T getPreferenceForCurrentUser(String preferenceKey) { User currentUser = securityService.getCurrentUser(); if (currentUser != null) { - return userStore.getPreferenceObject(currentUser.getName(), preferenceKey); + return securityService.getPreferenceObject(currentUser.getName(), preferenceKey); } return null; } - } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java index 1245a74695e..de1adc2a2f7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java @@ -15,7 +15,7 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; -import com.sap.sse.security.UserStore; +import com.sap.sse.security.SecurityService; @Path("/v1/preferences") public class PreferencesResource extends AbstractSailingServerResource { @@ -27,8 +27,8 @@ public class PreferencesResource extends AbstractSailingServerResource { Response response = null; if (SecurityUtils.getSubject().isAuthenticated()) { String username = SecurityUtils.getSubject().getPrincipal().toString(); - UserStore userStore = getService(UserStore.class); - String settings = userStore.getPreference(username, settingsKey); + SecurityService securityService = getService(SecurityService.class); + String settings = securityService.getPreference(username, settingsKey); if (settings == null) { response = Response.noContent().build(); } else { @@ -48,8 +48,8 @@ public class PreferencesResource extends AbstractSailingServerResource { Response response = null; if (SecurityUtils.getSubject().isAuthenticated()) { String username = SecurityUtils.getSubject().getPrincipal().toString(); - UserStore userStore = getService(UserStore.class); - userStore.setPreference(username, settingsKey, json); + SecurityService securityService = getService(SecurityService.class); + securityService.setPreference(username, settingsKey, json); response = Response.ok().build(); } else { response = Response.status(401).build(); @@ -65,8 +65,8 @@ public class PreferencesResource extends AbstractSailingServerResource { Response response = null; if (SecurityUtils.getSubject().isAuthenticated()) { String username = SecurityUtils.getSubject().getPrincipal().toString(); - UserStore userStore = getService(UserStore.class); - userStore.unsetPreference(username, settingsKey); + SecurityService securityService = getService(SecurityService.class); + securityService.unsetPreference(username, settingsKey); response = Response.ok().build(); } else { response = Response.status(401).build(); 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 92527a39cee..b26f4ba5d0f 100644 --- 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 @@ -158,6 +158,12 @@ public interface SecurityService extends ReplicableWithObjectInputStream see + * {@link #registerPreferenceConverter(String, PreferenceConverter)}. + */ + T getPreferenceObject(String username, String key); + /** * @return all preferences of the given user */ @@ -195,5 +201,4 @@ public interface SecurityService extends ReplicableWithObjectInputStream T getPreferenceObject(String username, String key) { + ensureThatUserInQuestionIsLoggedInOrCurrentUserIsAdmin(username); + return store.getPreferenceObject(username, key); + } } From 8e243f6a059b18cf4390f3cb65cdf8604cf88b20 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 14 Nov 2018 14:47:49 +0100 Subject: [PATCH 37/42] aligned ACL SecuredSecurityType with CHANGE_OWNERSHIP action by creating a CHANGE_ACL DefaultAction --- .../com/sap/sse/security/shared/HasPermissions.java | 5 +++-- .../security/shared/impl/SecuredSecurityTypes.java | 7 ------- .../component/DefaultActionsImagesBarCell.java | 9 +++++++++ .../ui/server/UserManagementServiceImpl.java | 13 +++++++++---- .../security/userstore/mongodb/impl/Activator.java | 2 -- 5 files changed, 21 insertions(+), 15 deletions(-) mode change 100755 => 100644 java/com.sap.sse.security.common/src/com/sap/sse/security/shared/HasPermissions.java diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/HasPermissions.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/HasPermissions.java old mode 100755 new mode 100644 index b08c0fd7d16..aaf7e179895 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/HasPermissions.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/HasPermissions.java @@ -91,9 +91,10 @@ public interface HasPermissions { * */ public enum DefaultActions implements Action { - CREATE, READ, UPDATE, DELETE, CHANGE_OWNERSHIP; + CREATE, READ, UPDATE, DELETE, CHANGE_OWNERSHIP, CHANGE_ACL; - public static final Action[] MUTATION_ACTIONS = new Action[] {CREATE, UPDATE, DELETE, CHANGE_OWNERSHIP}; + public static final Action[] MUTATION_ACTIONS = new Action[] { CREATE, UPDATE, DELETE, CHANGE_OWNERSHIP, + CHANGE_ACL }; public static final Action[] READ_AND_WRITE_ACTIONS = new Action[] { CREATE, READ, UPDATE, DELETE }; /** diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/SecuredSecurityTypes.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/SecuredSecurityTypes.java index 6bf8f5061cc..3ce9eb45940 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/SecuredSecurityTypes.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/impl/SecuredSecurityTypes.java @@ -5,7 +5,6 @@ import java.util.HashSet; import java.util.Set; import com.sap.sse.security.shared.HasPermissions; -import com.sap.sse.security.shared.QualifiedObjectIdentifier; import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.User; import com.sap.sse.security.shared.UserGroup; @@ -50,12 +49,6 @@ public class SecuredSecurityTypes extends HasPermissionsImpl { */ public static final HasPermissions USER_GROUP = new SecuredSecurityTypes("USER_GROUP"); - /** - * type-relative identifier is the string representation of the {@link QualifiedObjectIdentifier} of the object - * that the access control list governs access to. - */ - public static final HasPermissions ACCESS_CONTROL_LIST = new SecuredSecurityTypes("ACCESS_CONTROL_LIST"); - public static enum ServerActions implements Action { CONFIGURE_FILE_STORAGE, CONFIGURE_LOCAL_SERVER, 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 ec21b71e335..56e17723f58 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 @@ -12,6 +12,7 @@ public class DefaultActionsImagesBarCell extends ImagesBarCell { public static final String ACTION_DELETE = DefaultActions.DELETE.name(); public static final String ACTION_UPDATE = DefaultActions.UPDATE.name(); public static final String ACTION_CHANGE_OWNERSHIP = DefaultActions.CHANGE_OWNERSHIP.name(); + public static final String ACTION_CHANGE_ACL = DefaultActions.CHANGE_ACL.name(); private final StringMessages stringMessages; @@ -46,4 +47,12 @@ public class DefaultActionsImagesBarCell extends ImagesBarCell { IconResources.INSTANCE.changeOwnershipIcon()); } + /** + * @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()); + } + } \ No newline at end of file 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 1b5d87a689c..8b34d6c357d 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 @@ -166,9 +166,11 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public Collection getAccessControlLists() throws UnauthorizedException { + // TODO decide whether a global getAccessControlList functionality is needed List acls = new ArrayList<>(); for (AccessControlListAnnotation acl : getSecurityService().getAccessControlLists()) { - if (SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.ACCESS_CONTROL_LIST.getStringPermissionForObjects(DefaultActions.READ, acl.getIdOfAnnotatedObject().toString()))) { + if (SecurityUtils.getSubject() + .isPermitted(acl.getIdOfAnnotatedObject().getStringPermission(DefaultActions.CHANGE_ACL))) { acls.add(securityDTOFactory.createAccessControlListAnnotationDTO(acl)); } } @@ -177,13 +179,15 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public AccessControlListAnnotation getAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject) { - SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.ACCESS_CONTROL_LIST.getStringPermissionForObjects(DefaultActions.READ, idOfAccessControlledObject.toString())); + SecurityUtils.getSubject() + .checkPermission(idOfAccessControlledObject.getStringPermission(DefaultActions.CHANGE_ACL)); return securityDTOFactory.createAccessControlListAnnotationDTO(getSecurityService().getAccessControlList(idOfAccessControlledObject)); } @Override public AccessControlList updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, Map> permissionStrings) throws UnauthorizedException { - if (SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.ACCESS_CONTROL_LIST.getStringPermissionForObjects(DefaultActions.UPDATE, idOfAccessControlledObject.toString()))) { + if (SecurityUtils.getSubject() + .isPermitted(idOfAccessControlledObject.getStringPermission(DefaultActions.CHANGE_ACL))) { Map> permissionMap = new HashMap<>(); for (String group : permissionStrings.keySet()) { permissionMap.put(getSecurityService().getUserGroupByName(group), permissionStrings.get(group)); @@ -196,7 +200,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public AccessControlList addToAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, String groupIdAsString, String action) throws UnauthorizedException { - if (SecurityUtils.getSubject().isPermitted(SecuredSecurityTypes.ACCESS_CONTROL_LIST.getStringPermissionForObjects(DefaultActions.UPDATE, idOfAccessControlledObject.toString()))) { + if (SecurityUtils.getSubject() + .isPermitted(idOfAccessControlledObject.getStringPermission(DefaultActions.CHANGE_ACL))) { UserGroup userGroup = getUserGroup(groupIdAsString); return securityDTOFactory.createAccessControlListDTO(getSecurityService().addToAccessControlList(idOfAccessControlledObject, userGroup, action)); } else { diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java index bf77e0ef3c5..89f726f41ec 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/Activator.java @@ -65,8 +65,6 @@ public class Activator implements BundleActivator { for (UserGroup group : userStore.getUserGroups()) { securityService.migrateOwnership(group, SecuredSecurityTypes.getAllInstances()); } - securityService.assumeOwnershipMigrated(SecuredSecurityTypes.ACCESS_CONTROL_LIST.getName(), - SecuredSecurityTypes.getAllInstances()); securityService.assumeOwnershipMigrated(SecuredSecurityTypes.ROLE_DEFINITION.getName(), SecuredSecurityTypes.getAllInstances()); securityService.assumeOwnershipMigrated(SecuredSecurityTypes.SERVER.getName(), From 8d504ba1f37ef837efcbc3df94b2d8e0e2c88b98 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 14 Nov 2018 14:50:47 +0100 Subject: [PATCH 38/42] added pruning method to prune access control lists for a certain SecurityUser (remove all ACLs where the user is not in the associated UserGroup) before transmitting to FE --- .../ui/server/SecurityDTOFactory.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOFactory.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOFactory.java index 7f99cc32e4a..17023eb3ff0 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOFactory.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOFactory.java @@ -1,6 +1,7 @@ package com.sap.sse.security.ui.server; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -250,4 +251,26 @@ public class SecurityDTOFactory { } return result; } + + /** + * prunes the {@link AccessControlList} for the given {@link SecurityUser filterForUser} by removing all user groups + * the user is not in from the resulting ACL. + */ + public AccessControlList pruneAccessControlListForUser(AccessControlList acl, SecurityUser filterForUser) { + final AccessControlList result; + if (acl == null || filterForUser == null) { + result = acl; + } else { + final Collection userGroups = Util.createSet(filterForUser.getUserGroups()); + + final Map> actionsByUserGroup = new HashMap<>(); + for (final Entry> entry : acl.getActionsByUserGroup().entrySet()) { + if (userGroups.contains(entry.getKey())) { + actionsByUserGroup.put(entry.getKey(), entry.getValue()); + } + } + result = new AccessControlListImpl(actionsByUserGroup); + } + return result; + } } From 6abcdcf4c6f4ff14ad808bb6865ab2296083c4f8 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 14 Nov 2018 14:51:33 +0100 Subject: [PATCH 39/42] activated pruning of UserGroups in ACLs per default (deactivateable with disable Pruning flag --- .../sse/security/ui/server/SecurityDTOUtil.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOUtil.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOUtil.java index 3c1b85d9823..749ff7c5be8 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOUtil.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/SecurityDTOUtil.java @@ -57,7 +57,7 @@ public abstract class SecurityDTOUtil { final Map fromOriginalToStrippedDownUser = new HashMap<>(); final Map fromOriginalToStrippedDownUserGroup = new HashMap<>(); addSecurityInformation(securityDTOFactory, securityService, securedObject, objectId, - fromOriginalToStrippedDownUser, fromOriginalToStrippedDownUserGroup); + fromOriginalToStrippedDownUser, fromOriginalToStrippedDownUserGroup, false); } /** @@ -89,11 +89,18 @@ public abstract class SecurityDTOUtil { final SecurityService securityService, final SecuredObject securedObject, final QualifiedObjectIdentifier objectId, final Map fromOriginalToStrippedDownUser, - final Map fromOriginalToStrippedDownUserGroup) { + final Map fromOriginalToStrippedDownUserGroup, + final boolean disablePruningForCurrentUser) { final AccessControlListAnnotation accessControlList = securityService.getAccessControlList(objectId); - securedObject.setAccessControlList(securityDTOFactory.createAccessControlListDTO( + AccessControlList accessControlListDTO = securityDTOFactory.createAccessControlListDTO( accessControlList == null ? null : accessControlList.getAnnotation(), fromOriginalToStrippedDownUser, - fromOriginalToStrippedDownUserGroup)); + fromOriginalToStrippedDownUserGroup); + if (disablePruningForCurrentUser) { + securedObject.setAccessControlList(accessControlListDTO); + } else { + securedObject.setAccessControlList(securityDTOFactory.pruneAccessControlListForUser(accessControlListDTO, + securityService.getCurrentUser())); + } final OwnershipAnnotation ownership = securityService.getOwnership(objectId); securedObject.setOwnership( securityDTOFactory.createOwnershipDTO(ownership == null ? null : ownership.getAnnotation(), From c7e1a96f3d8004cc21b2a26d1ea8aac98916f7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Wed, 14 Nov 2018 15:31:18 +0100 Subject: [PATCH 40/42] split first time init code and wait for SecurityService to reduce complexity for RacingEventServiceImpl --- .../sap/sailing/server/impl/Activator.java | 187 +++++++++++------- .../server/impl/RacingEventServiceImpl.java | 38 ++-- 2 files changed, 140 insertions(+), 85 deletions(-) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index 1bd969019f8..da99d120fa2 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.impl; import java.lang.management.ManagementFactory; +import java.net.MalformedURLException; import java.nio.charset.Charset; import java.util.Dictionary; import java.util.HashSet; @@ -9,7 +10,11 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import org.osgi.framework.BundleActivator; @@ -117,80 +122,23 @@ public class Activator implements BundleActivator { mailServiceTracker = ServiceTrackerFactory.createAndOpen(context, MailService.class); securityServiceTracker = ServiceTrackerFactory.createAndOpen(context, SecurityService.class); - mailQueue = new ExecutorMailQueue(mailServiceTracker); - notificationService = new SailingNotificationServiceImpl(context, mailQueue); - trackedRegattaListener = new OSGiBasedTrackedRegattaListener(context); + if (securityServiceTracker != null) { + new Thread("Racingevent wait for securityservice for migration thread") { + public void run() { + try { + // only continue once we have the service, as some of the services require it to start properly + securityServiceTracker.waitForService(0); + internalStartBundle(context); + } catch (InterruptedException | MalformedObjectNameException | InstanceAlreadyExistsException + | MBeanRegistrationException | NotCompliantMBeanException | MalformedURLException e) { + logger.log(Level.SEVERE, "Could not start RacingEvent service properly", e); + } + }; + }.start(); + } - registrations.add(context.registerService(HasPermissionsProvider.class, - (HasPermissionsProvider) SecuredDomainType::getAllInstances, null)); - - registrations.add(context.registerService(RolePrototypeProvider.class, - (RolePrototypeProvider) SailingViewerRole::getInstance, null)); - final TrackedRaceStatisticsCache trackedRaceStatisticsCache = new TrackedRaceStatisticsCacheImpl(); - registrations.add(context.registerService(TrackedRaceStatisticsCache.class.getName(), trackedRaceStatisticsCache, null)); - registrations.add(context.registerService(TrackedRegattaListener.class.getName(), trackedRaceStatisticsCache, null)); - - // At this point the OSGi resolver is used as device type service finder. - // In the case that we are not in an OSGi context (e.g. running a JUnit test instead), - // this code block is not run, and the test case can inject some other type of finder - // instead. - serviceFinderFactory = new CachedOsgiTypeBasedServiceFinderFactory(context); - - - racingEventService = new RacingEventServiceImpl(clearPersistentCompetitors, serviceFinderFactory, - trackedRegattaListener, notificationService, trackedRaceStatisticsCache, restoreTrackedRaces, - securityServiceTracker); - notificationService.setRacingEventService(racingEventService); - - masterDataImportClassLoaderServiceTracker = new ServiceTracker( - context, MasterDataImportClassLoaderService.class, - new MasterDataImportClassLoaderServiceTrackerCustomizer(context, racingEventService)); - masterDataImportClassLoaderServiceTracker.open(); - - polarDataServiceTracker = new ServiceTracker(context, - PolarDataService.class, new PolarDataServiceTrackerCustomizer(context, racingEventService)); - polarDataServiceTracker.open(); - - // register the racing service in the OSGi registry - racingEventService.setBundleContext(context); - context.registerService(MongoObjectFactory.class, racingEventService.getMongoObjectFactory(), /* properties */ null); - context.registerService(DomainObjectFactory.class, racingEventService.getDomainObjectFactory(), /* properties */ null); - final Dictionary replicableServiceProperties = new Hashtable<>(); - replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, racingEventService.getId().toString()); - context.registerService(Replicable.class, racingEventService, replicableServiceProperties); - context.registerService(RacingEventService.class, racingEventService, null); - context.registerService(RaceLogResolver.class, racingEventService, null); - context.registerService(ClearStateTestSupport.class, racingEventService, null); - context.registerService(SensorFixStoreSupplier.class, racingEventService, null); - context.registerService(WindFinderReviewedSpotsCollectionIdProvider.class, racingEventService, null); - Dictionary properties = new Hashtable(); - final GPSFixMongoHandlerImpl gpsFixMongoHandler = new GPSFixMongoHandlerImpl( - racingEventService.getMongoObjectFactory(), racingEventService.getDomainObjectFactory()); - properties.put(TypeBasedServiceFinder.TYPE, GPSFixImpl.class.getName()); - registrations.add(context.registerService(FixMongoHandler.class, gpsFixMongoHandler, properties)); - // legacy type name; some DBs may still contain fixes marked with this old package name: - properties.put(TypeBasedServiceFinder.TYPE, "com.sap.sailing.domain.tracking.impl.GPSFixImpl"); - registrations.add(context.registerService(FixMongoHandler.class, gpsFixMongoHandler, properties)); - final GPSFixMovingMongoHandlerImpl gpsFixMovingMongoHandler = new GPSFixMovingMongoHandlerImpl( - racingEventService.getMongoObjectFactory(), racingEventService.getDomainObjectFactory()); - properties.put(TypeBasedServiceFinder.TYPE, GPSFixMovingImpl.class.getName()); - registrations.add(context.registerService(FixMongoHandler.class, gpsFixMovingMongoHandler, properties)); - // legacy type name; some DBs may still contain fixes marked with this old package name: - properties.put(TypeBasedServiceFinder.TYPE, "com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl"); - registrations.add(context.registerService(FixMongoHandler.class, gpsFixMovingMongoHandler, properties)); - properties.put(TypeBasedServiceFinder.TYPE, DoubleVectorFixImpl.class.getName()); - registrations.add(context.registerService(FixMongoHandler.class, new DoubleVectorFixMongoHandlerImpl( - racingEventService.getMongoObjectFactory(), racingEventService.getDomainObjectFactory()), properties)); - registerPreferenceConvertersForUserStore(context); - // Add an MBean for the service to the JMX bean server: - RacingEventServiceMXBean mbean = new RacingEventServiceMXBeanImpl(racingEventService); - MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); - mBeanName = new ObjectName("com.sap.sailing:type=RacingEventService"); - mbs.registerMBean(mbean, mBeanName); - logger.log(Level.INFO, "Started " + context.getBundle().getSymbolicName() + ". Character encoding: " - + Charset.defaultCharset()); } /** @@ -248,6 +196,103 @@ public class Activator implements BundleActivator { mbs.unregisterMBean(mBeanName); } + private void internalStartBundle(BundleContext context) throws MalformedURLException, MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { + mailQueue = new ExecutorMailQueue(mailServiceTracker); + notificationService = new SailingNotificationServiceImpl(context, mailQueue); + + trackedRegattaListener = new OSGiBasedTrackedRegattaListener(context); + + registrations.add(context.registerService(HasPermissionsProvider.class, + (HasPermissionsProvider) SecuredDomainType::getAllInstances, null)); + + registrations.add(context.registerService(RolePrototypeProvider.class, + (RolePrototypeProvider) SailingViewerRole::getInstance, null)); + + final TrackedRaceStatisticsCache trackedRaceStatisticsCache = new TrackedRaceStatisticsCacheImpl(); + registrations.add(context.registerService(TrackedRaceStatisticsCache.class.getName(), + trackedRaceStatisticsCache, null)); + registrations.add(context.registerService(TrackedRegattaListener.class.getName(), + trackedRaceStatisticsCache, null)); + + // At this point the OSGi resolver is used as device type service finder. + // In the case that we are not in an OSGi context (e.g. running a JUnit test instead), + // this code block is not run, and the test case can inject some other type of finder + // instead. + serviceFinderFactory = new CachedOsgiTypeBasedServiceFinderFactory(context); + + racingEventService = new RacingEventServiceImpl(clearPersistentCompetitors, + serviceFinderFactory, trackedRegattaListener, notificationService, + trackedRaceStatisticsCache, restoreTrackedRaces, securityServiceTracker); + notificationService.setRacingEventService(racingEventService); + + masterDataImportClassLoaderServiceTracker = new ServiceTracker( + context, MasterDataImportClassLoaderService.class, + new MasterDataImportClassLoaderServiceTrackerCustomizer(context, racingEventService)); + masterDataImportClassLoaderServiceTracker.open(); + + polarDataServiceTracker = new ServiceTracker(context, + PolarDataService.class, + new PolarDataServiceTrackerCustomizer(context, racingEventService)); + polarDataServiceTracker.open(); + + // register the racing service in the OSGi registry + racingEventService.setBundleContext(context); + context.registerService(MongoObjectFactory.class, racingEventService.getMongoObjectFactory(), + /* properties */ null); + context.registerService(DomainObjectFactory.class, racingEventService.getDomainObjectFactory(), + /* properties */ null); + final Dictionary replicableServiceProperties = new Hashtable<>(); + replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, + racingEventService.getId().toString()); + context.registerService(Replicable.class, racingEventService, replicableServiceProperties); + context.registerService(RacingEventService.class, racingEventService, null); + context.registerService(RaceLogResolver.class, racingEventService, null); + context.registerService(ClearStateTestSupport.class, racingEventService, null); + context.registerService(SensorFixStoreSupplier.class, racingEventService, null); + context.registerService(WindFinderReviewedSpotsCollectionIdProvider.class, racingEventService, + null); + Dictionary properties = new Hashtable(); + final GPSFixMongoHandlerImpl gpsFixMongoHandler = new GPSFixMongoHandlerImpl( + racingEventService.getMongoObjectFactory(), + racingEventService.getDomainObjectFactory()); + properties.put(TypeBasedServiceFinder.TYPE, GPSFixImpl.class.getName()); + registrations + .add(context.registerService(FixMongoHandler.class, gpsFixMongoHandler, properties)); + // legacy type name; some DBs may still contain fixes marked with this old package name: + properties.put(TypeBasedServiceFinder.TYPE, "com.sap.sailing.domain.tracking.impl.GPSFixImpl"); + registrations + .add(context.registerService(FixMongoHandler.class, gpsFixMongoHandler, properties)); + final GPSFixMovingMongoHandlerImpl gpsFixMovingMongoHandler = new GPSFixMovingMongoHandlerImpl( + racingEventService.getMongoObjectFactory(), + racingEventService.getDomainObjectFactory()); + properties.put(TypeBasedServiceFinder.TYPE, GPSFixMovingImpl.class.getName()); + registrations.add( + context.registerService(FixMongoHandler.class, gpsFixMovingMongoHandler, properties)); + // legacy type name; some DBs may still contain fixes marked with this old package name: + properties.put(TypeBasedServiceFinder.TYPE, + "com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl"); + registrations.add( + context.registerService(FixMongoHandler.class, gpsFixMovingMongoHandler, properties)); + properties.put(TypeBasedServiceFinder.TYPE, DoubleVectorFixImpl.class.getName()); + registrations.add(context.registerService(FixMongoHandler.class, + new DoubleVectorFixMongoHandlerImpl(racingEventService.getMongoObjectFactory(), + racingEventService.getDomainObjectFactory()), + properties)); + registerPreferenceConvertersForUserStore(context); + // Add an MBean for the service to the JMX bean server: + RacingEventServiceMXBean mbean = new RacingEventServiceMXBeanImpl(racingEventService); + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + mBeanName = new ObjectName("com.sap.sailing:type=RacingEventService"); + mbs.registerMBean(mbean, mBeanName); + logger.log(Level.INFO, "Started " + context.getBundle().getSymbolicName() + + ". Character encoding: " + Charset.defaultCharset()); + + // do initial setup/migration logic + racingEventService.ensureOwnerships(); + racingEventService.ensureServerIsInitiallyPublic(); + } + private class MasterDataImportClassLoaderServiceTrackerCustomizer implements ServiceTrackerCustomizer { 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 cbae53c9cc8..69b699295a0 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 @@ -253,6 +253,7 @@ import com.sap.sailing.server.operationaltransformation.UpdateStartTimeReceived; import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus; import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime; import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude; +import com.sap.sailing.server.security.SailingViewerRole; import com.sap.sailing.server.simulation.SimulationService; import com.sap.sailing.server.simulation.SimulationServiceFactory; import com.sap.sailing.server.statistics.StatisticsAggregator; @@ -288,6 +289,10 @@ import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; import com.sap.sse.security.SecurityService; +import com.sap.sse.security.shared.RoleDefinition; +import com.sap.sse.security.shared.RoleImpl; +import com.sap.sse.security.shared.User; +import com.sap.sse.security.shared.UserGroup; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.shared.media.VideoDescriptor; import com.sap.sse.util.ClearStateTestSupport; @@ -824,19 +829,23 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes new QuarterChecker(), new SameDigitChecker()); raceChangeObserverForAnniversaryDetection = new RaceChangeObserverForAnniversaryDetection(anniversaryRaceDeterminator); this.trackedRegattaListener.addListener(raceChangeObserverForAnniversaryDetection); - - if (securityServiceTracker != null) { - new Thread("Racingevent wait for securityservice for migration thread") { - public void run() { - SecurityService securityService; - try { - securityService = securityServiceTracker.waitForService(0); - ensureOwnerships(securityService); - } catch (InterruptedException e) { - logger.warning("Could not obtain SecurityService " + e.getMessage()); - } - }; - }.start(); + } + + public void ensureServerIsInitiallyPublic() { + try { + final User allUser = getSecurityService().getAllUser(); + String initializedKey = ("serverInitialized " + ServerInfo.getName()).replaceAll("[\\W]|_", ""); + if (!Boolean.TRUE.equals(getSecurityService().getSetting(initializedKey, Boolean.class))) { + getSecurityService().addSetting(initializedKey, Boolean.class); + final RoleDefinition viewerRole = getSecurityService() + .getRoleDefinition(SailingViewerRole.getInstance().getId()); + final UserGroup defaultServerTenant = getSecurityService().getDefaultTenant(); + final RoleImpl publicAccessForServerRole = new RoleImpl(viewerRole, defaultServerTenant, null); + getSecurityService().addRoleForUser(allUser.getName(), publicAccessForServerRole); + getSecurityService().setSetting(initializedKey, true); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Error determining Server initialisation state", e); } } @@ -933,7 +942,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes return domainObjectFactory; } - private void ensureOwnerships(SecurityService securityService) { + public void ensureOwnerships() { + SecurityService securityService = getSecurityService(); securityService.assumeOwnershipMigrated(SecuredDomainType.MANAGE_MARK_PASSINGS.getName(), SecuredDomainType.getAllInstances()); securityService.assumeOwnershipMigrated(SecuredDomainType.MANAGE_MARK_POSITIONS.getName(), From f020b6b7f97da51d5deb1b52f76401049355d8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Sch=C3=A4fer?= Date: Wed, 14 Nov 2018 15:31:34 +0100 Subject: [PATCH 41/42] Updated permission concept (e.g. regarding sharing) --- wiki/info/security/permission-concept.md | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/wiki/info/security/permission-concept.md b/wiki/info/security/permission-concept.md index 24c10439491..4a266269def 100755 --- a/wiki/info/security/permission-concept.md +++ b/wiki/info/security/permission-concept.md @@ -91,11 +91,13 @@ This role implies the "*" permission. It should ideally be used with a tenant qu ### User "admin" -### Role for Anonymous Users +On fresh instances that do not use a shared UserStore, the user "admin" is automatically created if no users exist yet on this instance. This user has the unqualified "admin" permission associated, which means this user has the permission to do everything. On event servers, this user is typically used to give permissions to specific event admin user. + +### User "" When a user has not yet been authenticated, certain actions still need to be allowed for such users, in particular viewing public events or creating a new user during a sign-up activity. One approach may be to simply not request permissions for such actions. This way, all sessions would be able to perform such actions. The downside: in no server configuration would it be possible to limit access to those actions. For example, if one wanted to set up a private server instance where user sign-up is to be allowed only for administrators of that server then this could not be solved by role and permission configuration. -It would be more flexible if there was a role that all anonymous users implicitly have. This role could then by default imply the permissions that we want anonymous users to have. If a special server set-up demands changes then this could easily be solved by modifying the permissions of this role. +To allow specific permissions to get associated on specific servers, a user named "" has been introduced. Any permissions/roles associated with this user are evaluated for any user (including non-authenticated ones). ## Administration of Authorization @@ -118,25 +120,25 @@ Tenant ownership has implications only for the application of roles that are qua ### Implementation of Sharing Data Objects with Public -In general, sharing a data object with the public should just be granting the “view” permission to everyone. For this purpose, the ```` user can be assigned the corresponding ``READ`` permission. This in return allows everybody that knows the link where one can view the data object to view it. +To make sharing as convenient as possible it was decided to use groups and their owned objects as basic unit of sharing. This means all domain objects of specific types owned by a common group (e.g. an event specific group) are visible to all users having a specific "viewer" role for that group. -Alternatively, an access control list (ACL) could be assigned to the object, using a ``null`` group assignment, thereby making this ACL applicable to all users, regardless of their group memberships. The ACL will also be checked for permission requests by not authenticated users. +A domain specific "viewer" role needs to be defined, having the required "READ" permissions for all object types that are open to a broader audience (e.g. events, but no advanced data). For Sailing Analytics, this role is named "sailing_viewer". The definition of a generic "viewer" role (e.g. having the permission "*:READ") is impracticabledue to the fact that this also allows read access to internal configuration objects and other administrative data. This means the associated permissions need to be explicitly enumerated. -The remaining challenge is the application of the permissions necessary for *all* objects in the *scope* that is to be shared. A user it typically not interested in sharing only the bare *Event* object but would rather want to grant public access to everything *belonging to* the event, including all leaderboard groups, leaderboards, regattas and tracked races. +To share the domain objects being owned by a group, you need to to associate the specific "viewer" role to a user with a qualification by the owning group. To make an event public, you can instead give the qualified "viewer" role to the "" user. -The problem with this is that this object set is dynamic. It changes as users add object to or remove them from the scope of the event. For example, if another race is being added after the event has been shared, a reasonable behavior seems to be that the new race shall also be shared together with everything else belonging to the overarching event that has been shared before. This is similar to a "setgid" bit in a Unix file system. +To make the role work as intended, the domain objects of an event to be shared need to be consistently owned by a specific group. To make this as convenient as possible and to be able to fix wrong ownerships, batch-changing the group owner of related domain objects needs to be able. For Sailing Analytics, this means batch-changing needs to be possible under the following conditions: +* Batch-changing the ownership of a LeaderboardGroup in general includes Leaderboards/Regattas and their associated TrackedRaces. +* For event series defined by a LeaderboardGroup having an overall leaderboard, a batch change will also catch all events associated to the series. +* Batch-changing the ownership of an event means, all LeaderboardGroups not having an overall leaderboard are included with their included Leaderboards/Regattas and associated TrackedRaces. +* Such a batch change in general does not include Competitors/Boats that are used by a regatta. This is explicitly intended because Competitors/Boats are typically used on different event/regatta contexts that are not directly related. In some cases this could in fact be useful which means a user needs to have the choice of optionally include Competitors/Boats. -For this to work we will need observable object relationships which can trigger rules for permission propagation along the relationships. While we agreed that the permission checking mechanism must not need to analyze object relationships, changes in relationships may lead to updates in permissions. +In addition we need to detect object associations with inconsistent group ownerships. In most cases domain objects getting associated in one of the hierarchies mentioned above, also need to inherit the group ownership if the associated "higher level" object is the first. For Sailing analytics, this means a leaderboard being added to a leaderboard group will inherit the leaderboard group's group owner if the leaderboard is not yet associated to another leaderboard group (assumed the current user is permitted to change the leaderboard's ownership). This ensures that e.g. leaderboards being created in the personal group of a user will get "fixed" when adding them to an event-context. The user needs to be informed when auto-adjusting group ownerships on association as well as cases where such an auto update is not performed. It needs to be discussed how the REST APIs can do this properly. -Examples: when a user links a leaderboard group to an event that is visible to a larger set of users than the leaderboard group, the user may receive a hint suggesting to update the leaderboard group's permissions to match those of the event into which it is just being linked. Similarly, if a leaderboard is added to a leaderboard group, the user may want to propagate the leaderboard group's visibility to that of the leaderboard just added to the group. - -Possible approach: make directed permission propagation a property on associations between domain types that users can configure. Such associations then need to be observed by the permission propagation mechanism, and when links are added (what if they are removed?) the permission propagation is triggered. Furthermore, permission propagation may need to be triggered when the permissions on a source object of a permission propagation change, in order to support transitive permission propagation, e.g., when a LeaderboardGroup is added to an Event, so that also all Leaderboards and Regattas and TrackedRaces reachable from that LeaderboardGroup receive the same permission update as the LeaderboardGroup itself. - -Propagation rule candidates: Event---LeaderboardGroup; LeaderboardGroup---Leaderboard; Leaderboard---Regatta; Leaderboard---TrackedRace. - -This is separated from promoting this public data object (e.g. event) on the official SAP site. This would have to be a separate list which can be edited by e.g. media admins. This would however only link to the source, because otherwise all promoted material would have to be imported to the archive. +An additional requirement is the possibility to extract an event/series from a bigger sharing context (e.g. club group). This means a new group needs to be created and the domain objects's group owner need to be changed to this new group by following the structure explained above. In addition, all role associations that exist for the source group need to be copied as equivalent role associations for the newly created group to ensure that all users having specific permissions for the source group will keep their respective permissions. ACLs do not need to be adjusted due to the fact that those are directly associated to the respective objects that will keep their object IDs. +The downside of a role-based approach for sharing is that sharing domain objects requires every single user to have the specific role associated for the owning group. It is currently not possible to give roles to all users of a group. In contrast to this, sharing domain object with the public is easy. +In addition of sharing domain objects of a whole event, there is also the need to share single races. This can easily solved by using ACLs on the respective TrackedRace instance. Be aware that this approach is not conveniently usable for a hierarchy of domain objects due to the fact that this would require ACL rules to be implicitly batch-updated for all objects of a hierarchy. In addition, a consistent view of ACLs in a hierarchy can not easily created in contrast of just finding differences in the group ownerships of domain objects. ## Permissions in Frontend From 6a2916bead120cff63b5d2471647ab1244d78893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Wed, 14 Nov 2018 16:18:35 +0100 Subject: [PATCH 42/42] fixed missing dependencies for tests --- .../META-INF/MANIFEST.MF | 3 ++- java/com.sap.sailing.server.test/META-INF/MANIFEST.MF | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.replication.test/META-INF/MANIFEST.MF index 4c21a0ad52c..3346f7242f6 100644 --- a/java/com.sap.sailing.server.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.replication.test/META-INF/MANIFEST.MF @@ -32,5 +32,6 @@ Require-Bundle: com.sap.sailing.server, javax.ws.rs, com.sap.sailing.server.testsupport, com.sap.sse.security, - com.sap.sse.security.common + com.sap.sse.security.common, + com.sun.jersey;bundle-version="1.17.0" Automatic-Module-Name: com.sap.sailing.server.replication.test diff --git a/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF index 16a32cd52a9..50375521890 100644 --- a/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF @@ -23,7 +23,8 @@ Require-Bundle: com.sap.sailing.domain, org.eclipse.jetty.server;bundle-version="9.4.8", com.sap.sailing.domain.shared.android, com.sap.sailing.domain.racelogtrackingadapter.testsupport, - com.sap.sailing.server.testsupport + com.sap.sailing.server.testsupport, + com.sun.jersey;bundle-version="1.17.0" Import-Package: com.sap.sailing.server.gateway.jaxrs, com.sap.sailing.server.gateway.jaxrs.spi, com.sap.sse.security.userstore.mongodb,