From 0d837773359b4d8db93b523eb5f0c5a59df675ea Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:19:44 +0200 Subject: [PATCH 01/21] Bug 5049: added POST role and GET role endpoints to REST API to support managing a role via the REST API --- java/com.sap.sse.security/resources/shiro.ini | 1 + .../security/jaxrs/api/SecurityResource.java | 121 ++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index 415ae5cf2b8..6e47e904c3c 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -72,3 +72,4 @@ bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter /api/restsecurity/access_token = bearerToken /api/restsecurity/remove_access_token = bearerToken /api/restsecurity/hello = bearerToken +/api/restsecurity/role = bearerToken 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 a6764da4606..897764ffa83 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,6 +1,10 @@ package com.sap.sse.security.jaxrs.api; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.Callable; import javax.ws.rs.DELETE; @@ -19,13 +23,17 @@ import javax.ws.rs.core.UriInfo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.sap.sse.common.Util; import com.sap.sse.common.mail.MailException; import com.sap.sse.security.jaxrs.AbstractSecurityResource; +import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.shared.WildcardPermission; import com.sap.sse.security.shared.impl.SecuredSecurityTypes; import com.sap.sse.security.shared.impl.User; import com.sun.jersey.api.client.ClientResponse.Status; @@ -283,6 +291,99 @@ public class SecurityResource extends AbstractSecurityResource { return result; } + @POST + @Path("/role") + @Produces("text/plain;charset=UTF-8") + public Response updatePermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String queryRoleId, + @FormParam("roleId") String formRoleId, @QueryParam("permissions") List queryPermissions, + @FormParam("permissions") List formPermissions) { + + final String roleId = preferFirstIfNotNullOrElseSecond(formRoleId, queryRoleId); + final List permissionStrings = preferFirstIfNotEmptyOrElseSecond(formPermissions, queryPermissions); + + Response resp; + try { + + // parse UUID + final UUID roleUUID = UUID.fromString(roleId); + + // get role definition from role id + RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + + // null check role definition + if (roleDefinition == null) { + resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) + .build(); + } else { + // check update permission on role + getService().checkCurrentUserUpdatePermission(roleDefinition); + + Set permissions = new HashSet<>(); + for (String permission : permissionStrings) { + permissions.add(new WildcardPermission(permission)); + } + + Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); + addedPermissions.removeAll(permissions); + + if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, + addedPermissions)) { + resp = Response.status(Status.UNAUTHORIZED) + .entity("Not permitted to grant permissions for role " + roleDefinition.getName()).build(); + } else { + roleDefinition.setPermissions(permissions); + getService().updateRoleDefinition(roleDefinition); + resp = Response.ok().build(); + } + } + } catch (IllegalArgumentException e) { + resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); + } catch (UnauthorizedException e) { + resp = Response.status(Status.UNAUTHORIZED).build(); + } + return resp; + } + + @GET + @Path("/role") + @Produces("application/json;charset=UTF-8") + public Response getPermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { + + Response resp; + try { + + // parse UUID + final UUID roleUUID = UUID.fromString(roleId); + + // get role definition from role id + RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + + // null check role definition + if (roleDefinition == null) { + resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) + .build(); + } else { + // check read permission on role + getService().checkCurrentUserReadPermission(roleDefinition); + + // build json result + JSONObject jsonResult = new JSONObject(); + JSONArray jsonPermissions = new JSONArray(); + for (WildcardPermission permission : roleDefinition.getPermissions()) { + jsonPermissions.add(permission.toString()); + } + jsonResult.put("permissions", jsonPermissions); + jsonResult.put("id", roleId); + resp = Response.ok(jsonResult.toJSONString()).build(); + } + } catch (IllegalArgumentException e) { + resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); + } catch (UnauthorizedException e) { + resp = Response.status(Status.UNAUTHORIZED).build(); + } + return resp; + } + Response respondToRemoveAccessTokenForUser(final String username) { final Response result; getService().removeAccessToken(username); @@ -319,4 +420,24 @@ public class SecurityResource extends AbstractSecurityResource { response.put("access_token", accessToken); return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } + + private T preferFirstIfNotNullOrElseSecond(T first, T second) { + final T result; + if (first != null) { + result = first; + } else { + result = second; + } + return result; + } + + private List preferFirstIfNotEmptyOrElseSecond(List first, List second) { + final List result; + if (first != null && !first.isEmpty()) { + result = first; + } else { + result = second; + } + return result; + } } \ No newline at end of file From f9ebd12186591f3aa90922d2c2d38c81850314f1 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:23:31 +0200 Subject: [PATCH 02/21] Bug 5049: added javadoc in SecurityResource --- .../sse/security/jaxrs/api/SecurityResource.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 897764ffa83..14a9677ad53 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 @@ -308,7 +308,7 @@ public class SecurityResource extends AbstractSecurityResource { final UUID roleUUID = UUID.fromString(roleId); // get role definition from role id - RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + final RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); // null check role definition if (roleDefinition == null) { @@ -318,11 +318,13 @@ public class SecurityResource extends AbstractSecurityResource { // check update permission on role getService().checkCurrentUserUpdatePermission(roleDefinition); - Set permissions = new HashSet<>(); - for (String permission : permissionStrings) { - permissions.add(new WildcardPermission(permission)); + // create permission objects + final Set permissions = new HashSet<>(); + for (String permissionString : permissionStrings) { + permissions.add(new WildcardPermission(permissionString)); } + // check only those metea-permissions which changed Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); addedPermissions.removeAll(permissions); @@ -331,6 +333,7 @@ public class SecurityResource extends AbstractSecurityResource { resp = Response.status(Status.UNAUTHORIZED) .entity("Not permitted to grant permissions for role " + roleDefinition.getName()).build(); } else { + // update role definitino with new permissions roleDefinition.setPermissions(permissions); getService().updateRoleDefinition(roleDefinition); resp = Response.ok().build(); @@ -366,7 +369,7 @@ public class SecurityResource extends AbstractSecurityResource { // check read permission on role getService().checkCurrentUserReadPermission(roleDefinition); - // build json result + // build json result with permissions and id JSONObject jsonResult = new JSONObject(); JSONArray jsonPermissions = new JSONArray(); for (WildcardPermission permission : roleDefinition.getPermissions()) { @@ -421,6 +424,7 @@ public class SecurityResource extends AbstractSecurityResource { return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } + /** Returns the second object if the first is null. */ private T preferFirstIfNotNullOrElseSecond(T first, T second) { final T result; if (first != null) { @@ -431,6 +435,7 @@ public class SecurityResource extends AbstractSecurityResource { return result; } + /** Selects the second list if the first list is null or empty. */ private List preferFirstIfNotEmptyOrElseSecond(List first, List second) { final List result; if (first != null && !first.isEmpty()) { From a409df04bde5c9147033e7634dc6294029393301 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:32:11 +0200 Subject: [PATCH 03/21] Bug 5049: moved role-related REST endpoints to RoleResource.java --- .../jaxrs/api/RestApiApplication.java | 1 + .../sse/security/jaxrs/api/RoleResource.java | 145 ++++++++++++++++++ .../security/jaxrs/api/SecurityResource.java | 126 --------------- 3 files changed, 146 insertions(+), 126 deletions(-) create mode 100644 java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RestApiApplication.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RestApiApplication.java index f5f49392f2d..18328c2c3f4 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RestApiApplication.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RestApiApplication.java @@ -11,6 +11,7 @@ public class RestApiApplication extends Application { public Set> getClasses() { HashSet> classes = new HashSet<>(); classes.add(SecurityResource.class); + classes.add(RoleResource.class); // exception mapper classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java new file mode 100644 index 00000000000..f3c647c19a6 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -0,0 +1,145 @@ +package com.sap.sse.security.jaxrs.api; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +import org.apache.shiro.authz.UnauthorizedException; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sse.security.jaxrs.AbstractSecurityResource; +import com.sap.sse.security.shared.RoleDefinition; +import com.sap.sse.security.shared.WildcardPermission; + +@Path("/restsecurity/role") +public class RoleResource extends AbstractSecurityResource { + + @POST + @Produces("text/plain;charset=UTF-8") + public Response updatePermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String queryRoleId, + @FormParam("roleId") String formRoleId, @QueryParam("permissions") List queryPermissions, + @FormParam("permissions") List formPermissions) { + + final String roleId = preferFirstIfNotNullOrElseSecond(formRoleId, queryRoleId); + final List permissionStrings = preferFirstIfNotEmptyOrElseSecond(formPermissions, queryPermissions); + + Response resp; + try { + + // parse UUID + final UUID roleUUID = UUID.fromString(roleId); + + // get role definition from role id + final RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + + // null check role definition + if (roleDefinition == null) { + resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) + .build(); + } else { + // check update permission on role + getService().checkCurrentUserUpdatePermission(roleDefinition); + + // create permission objects + final Set permissions = new HashSet<>(); + for (String permissionString : permissionStrings) { + permissions.add(new WildcardPermission(permissionString)); + } + + // check only those metea-permissions which changed + Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); + addedPermissions.removeAll(permissions); + + if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, + addedPermissions)) { + resp = Response.status(Status.UNAUTHORIZED) + .entity("Not permitted to grant permissions for role " + roleDefinition.getName()).build(); + } else { + // update role definitino with new permissions + roleDefinition.setPermissions(permissions); + getService().updateRoleDefinition(roleDefinition); + resp = Response.ok().build(); + } + } + } catch (IllegalArgumentException e) { + resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); + } catch (UnauthorizedException e) { + resp = Response.status(Status.UNAUTHORIZED).build(); + } + return resp; + } + + @GET + @Produces("application/json;charset=UTF-8") + public Response getPermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { + + Response resp; + try { + + // parse UUID + final UUID roleUUID = UUID.fromString(roleId); + + // get role definition from role id + RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + + // null check role definition + if (roleDefinition == null) { + resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) + .build(); + } else { + // check read permission on role + getService().checkCurrentUserReadPermission(roleDefinition); + + // build json result with permissions and id + JSONObject jsonResult = new JSONObject(); + JSONArray jsonPermissions = new JSONArray(); + for (WildcardPermission permission : roleDefinition.getPermissions()) { + jsonPermissions.add(permission.toString()); + } + jsonResult.put("permissions", jsonPermissions); + jsonResult.put("id", roleId); + resp = Response.ok(jsonResult.toJSONString()).build(); + } + } catch (IllegalArgumentException e) { + resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); + } catch (UnauthorizedException e) { + resp = Response.status(Status.UNAUTHORIZED).build(); + } + return resp; + } + + /** Returns the second object if the first is null. */ + private T preferFirstIfNotNullOrElseSecond(T first, T second) { + final T result; + if (first != null) { + result = first; + } else { + result = second; + } + return result; + } + + /** Selects the second list if the first list is null or empty. */ + private List preferFirstIfNotEmptyOrElseSecond(List first, List second) { + final List result; + if (first != null && !first.isEmpty()) { + result = first; + } else { + result = second; + } + return result; + } +} 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 14a9677ad53..a6764da4606 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,10 +1,6 @@ package com.sap.sse.security.jaxrs.api; -import java.util.HashSet; -import java.util.List; import java.util.Locale; -import java.util.Set; -import java.util.UUID; import java.util.concurrent.Callable; import javax.ws.rs.DELETE; @@ -23,17 +19,13 @@ import javax.ws.rs.core.UriInfo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; -import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; -import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.sap.sse.common.Util; import com.sap.sse.common.mail.MailException; import com.sap.sse.security.jaxrs.AbstractSecurityResource; -import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.UserManagementException; -import com.sap.sse.security.shared.WildcardPermission; import com.sap.sse.security.shared.impl.SecuredSecurityTypes; import com.sap.sse.security.shared.impl.User; import com.sun.jersey.api.client.ClientResponse.Status; @@ -291,102 +283,6 @@ public class SecurityResource extends AbstractSecurityResource { return result; } - @POST - @Path("/role") - @Produces("text/plain;charset=UTF-8") - public Response updatePermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String queryRoleId, - @FormParam("roleId") String formRoleId, @QueryParam("permissions") List queryPermissions, - @FormParam("permissions") List formPermissions) { - - final String roleId = preferFirstIfNotNullOrElseSecond(formRoleId, queryRoleId); - final List permissionStrings = preferFirstIfNotEmptyOrElseSecond(formPermissions, queryPermissions); - - Response resp; - try { - - // parse UUID - final UUID roleUUID = UUID.fromString(roleId); - - // get role definition from role id - final RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); - - // null check role definition - if (roleDefinition == null) { - resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) - .build(); - } else { - // check update permission on role - getService().checkCurrentUserUpdatePermission(roleDefinition); - - // create permission objects - final Set permissions = new HashSet<>(); - for (String permissionString : permissionStrings) { - permissions.add(new WildcardPermission(permissionString)); - } - - // check only those metea-permissions which changed - Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); - addedPermissions.removeAll(permissions); - - if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, - addedPermissions)) { - resp = Response.status(Status.UNAUTHORIZED) - .entity("Not permitted to grant permissions for role " + roleDefinition.getName()).build(); - } else { - // update role definitino with new permissions - roleDefinition.setPermissions(permissions); - getService().updateRoleDefinition(roleDefinition); - resp = Response.ok().build(); - } - } - } catch (IllegalArgumentException e) { - resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); - } catch (UnauthorizedException e) { - resp = Response.status(Status.UNAUTHORIZED).build(); - } - return resp; - } - - @GET - @Path("/role") - @Produces("application/json;charset=UTF-8") - public Response getPermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { - - Response resp; - try { - - // parse UUID - final UUID roleUUID = UUID.fromString(roleId); - - // get role definition from role id - RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); - - // null check role definition - if (roleDefinition == null) { - resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) - .build(); - } else { - // check read permission on role - getService().checkCurrentUserReadPermission(roleDefinition); - - // build json result with permissions and id - JSONObject jsonResult = new JSONObject(); - JSONArray jsonPermissions = new JSONArray(); - for (WildcardPermission permission : roleDefinition.getPermissions()) { - jsonPermissions.add(permission.toString()); - } - jsonResult.put("permissions", jsonPermissions); - jsonResult.put("id", roleId); - resp = Response.ok(jsonResult.toJSONString()).build(); - } - } catch (IllegalArgumentException e) { - resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); - } catch (UnauthorizedException e) { - resp = Response.status(Status.UNAUTHORIZED).build(); - } - return resp; - } - Response respondToRemoveAccessTokenForUser(final String username) { final Response result; getService().removeAccessToken(username); @@ -423,26 +319,4 @@ public class SecurityResource extends AbstractSecurityResource { response.put("access_token", accessToken); return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } - - /** Returns the second object if the first is null. */ - private T preferFirstIfNotNullOrElseSecond(T first, T second) { - final T result; - if (first != null) { - result = first; - } else { - result = second; - } - return result; - } - - /** Selects the second list if the first list is null or empty. */ - private List preferFirstIfNotEmptyOrElseSecond(List first, List second) { - final List result; - if (first != null && !first.isEmpty()) { - result = first; - } else { - result = second; - } - return result; - } } \ No newline at end of file From 2f8cfee9ced8c7f79d150baaa872b5321e204371 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:33:47 +0200 Subject: [PATCH 04/21] Bug 5049: minor cleanup in RoleResource --- .../com/sap/sse/security/jaxrs/api/RoleResource.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index f3c647c19a6..5bb3367628f 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -55,12 +55,12 @@ public class RoleResource extends AbstractSecurityResource { // create permission objects final Set permissions = new HashSet<>(); - for (String permissionString : permissionStrings) { + for (final String permissionString : permissionStrings) { permissions.add(new WildcardPermission(permissionString)); } // check only those metea-permissions which changed - Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); + final Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); addedPermissions.removeAll(permissions); if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, @@ -93,7 +93,7 @@ public class RoleResource extends AbstractSecurityResource { final UUID roleUUID = UUID.fromString(roleId); // get role definition from role id - RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + final RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); // null check role definition if (roleDefinition == null) { @@ -104,9 +104,9 @@ public class RoleResource extends AbstractSecurityResource { getService().checkCurrentUserReadPermission(roleDefinition); // build json result with permissions and id - JSONObject jsonResult = new JSONObject(); - JSONArray jsonPermissions = new JSONArray(); - for (WildcardPermission permission : roleDefinition.getPermissions()) { + final JSONObject jsonResult = new JSONObject(); + final JSONArray jsonPermissions = new JSONArray(); + for (final WildcardPermission permission : roleDefinition.getPermissions()) { jsonPermissions.add(permission.toString()); } jsonResult.put("permissions", jsonPermissions); From 2b6f3b259266ff7679b454c760186fa105171c86 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:42:03 +0200 Subject: [PATCH 05/21] Bug 5049: added API doc for RoleResouce --- .../webservices/api/role.html | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 java/com.sap.sse.security/webservices/api/role.html diff --git a/java/com.sap.sse.security/webservices/api/role.html b/java/com.sap.sse.security/webservices/api/role.html new file mode 100644 index 00000000000..5e776f13186 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/role.html @@ -0,0 +1,62 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

SAP Sponsorships Engine Security Webservices API

+

URL: /api/restsecurity/role

+ +Description: +

The service reads and updates a role and the associated permissions based on the HTTP method used.

+
+ + + + + + + + + + + + + + + + + +
Request Method:GET
Output format:a JSON document of the following format
+
{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587"}
+
Mandatory parameters: +
roleId: UUID of the role to get.
+
Example:curl -X GET "127.0.0.1:8888/security/api/restsecurity/role?roleId=29914406-b0fc-43cd-acc7-62a83931f587"
+ Will provide a JSON document as explained above.
+ + + + + + + + + + + + + + + +
Request Method:POST
Output format:An empty response with status code 200 in case of success; error-indicating status codes otherwise +
Mandatory parameters: +
roleId: UUID of the role to update.
+
permissions: Set of String permissions, the role's current permissions will be replaced with.
+
Example:curl -X POST "127.0.0.1:8888/security/api/restsecurity/role?roleId=29914406-b0fc-43cd-acc7-62a83931f587&permissions=BOAT:READ&permissions=COMPETITOR,EVENT:READ"
+
+ + From 293b0e15e3f3dc3732c9b5380e882f30c803fb04 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Wed, 22 May 2019 16:46:38 +0200 Subject: [PATCH 06/21] Bug 5049: added name of role to GET endpoint in RoleResource --- .../src/com/sap/sse/security/jaxrs/api/RoleResource.java | 3 ++- java/com.sap.sse.security/webservices/api/role.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 5bb3367628f..153c2ab33d7 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -84,7 +84,7 @@ public class RoleResource extends AbstractSecurityResource { @GET @Produces("application/json;charset=UTF-8") - public Response getPermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { + public Response getRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { Response resp; try { @@ -111,6 +111,7 @@ public class RoleResource extends AbstractSecurityResource { } jsonResult.put("permissions", jsonPermissions); jsonResult.put("id", roleId); + jsonResult.put("name", roleDefinition.getName()); resp = Response.ok(jsonResult.toJSONString()).build(); } } catch (IllegalArgumentException e) { diff --git a/java/com.sap.sse.security/webservices/api/role.html b/java/com.sap.sse.security/webservices/api/role.html index 5e776f13186..6c2ebcb09e5 100644 --- a/java/com.sap.sse.security/webservices/api/role.html +++ b/java/com.sap.sse.security/webservices/api/role.html @@ -22,7 +22,7 @@ Output format: a JSON document of the following format
-
{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587"}
+
{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}
From 958eacce9f480c7ec1c462c6e71eeabd7dff217b Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Mon, 27 May 2019 15:03:01 +0200 Subject: [PATCH 07/21] Bug 5049: Implemented meta permission check for removal of a permission from a role --- .../sse/security/ui/server/UserManagementServiceImpl.java | 8 ++++++++ .../src/com/sap/sse/security/jaxrs/api/RoleResource.java | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) 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 16628d1e23c..ebb45867a03 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 @@ -181,6 +181,14 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U + roleDefinitionWithNewProperties.getName()); } + Set removedPermissions = new HashSet<>(existingRole.getPermissions()); + removedPermissions.removeAll(roleDefinitionWithNewProperties.getPermissions()); + + if (!getSecurityService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(existingRole, removedPermissions)) { + throw new UnauthorizedException("Not permitted to revoke permissions for role " + + roleDefinitionWithNewProperties.getName()); + } + getSecurityService().updateRoleDefinition(roleDefinitionWithNewProperties); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 153c2ab33d7..f0131536bd0 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -59,14 +59,20 @@ public class RoleResource extends AbstractSecurityResource { permissions.add(new WildcardPermission(permissionString)); } - // check only those metea-permissions which changed + // check only those meta-permissions which changed final Set addedPermissions = new HashSet<>(roleDefinition.getPermissions()); addedPermissions.removeAll(permissions); + final Set removedPermissions = new HashSet<>(permissions); + removedPermissions.removeAll(roleDefinition.getPermissions()); if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, addedPermissions)) { resp = Response.status(Status.UNAUTHORIZED) .entity("Not permitted to grant permissions for role " + roleDefinition.getName()).build(); + } else if (!getService().hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(roleDefinition, + removedPermissions)) { + resp = Response.status(Status.UNAUTHORIZED) + .entity("Not permitted to revoke permissions for role " + roleDefinition.getName()).build(); } else { // update role definitino with new permissions roleDefinition.setPermissions(permissions); From d48a3ac31d5e13df1c28ade1d94e7dae82be78d6 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 12:50:25 +0200 Subject: [PATCH 08/21] Bug 5049: removed support for query parameters for /role rest endpoint --- .../sse/security/jaxrs/api/RoleResource.java | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index f0131536bd0..b9825c215de 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -29,12 +29,8 @@ public class RoleResource extends AbstractSecurityResource { @POST @Produces("text/plain;charset=UTF-8") - public Response updatePermissionsForRole(@Context UriInfo uriInfo, @QueryParam("roleId") String queryRoleId, - @FormParam("roleId") String formRoleId, @QueryParam("permissions") List queryPermissions, - @FormParam("permissions") List formPermissions) { - - final String roleId = preferFirstIfNotNullOrElseSecond(formRoleId, queryRoleId); - final List permissionStrings = preferFirstIfNotEmptyOrElseSecond(formPermissions, queryPermissions); + public Response updatePermissionsForRole(@Context UriInfo uriInfo, @FormParam("roleId") String roleId, + @FormParam("permissions") List permissionStrings) { Response resp; try { @@ -127,26 +123,4 @@ public class RoleResource extends AbstractSecurityResource { } return resp; } - - /** Returns the second object if the first is null. */ - private T preferFirstIfNotNullOrElseSecond(T first, T second) { - final T result; - if (first != null) { - result = first; - } else { - result = second; - } - return result; - } - - /** Selects the second list if the first list is null or empty. */ - private List preferFirstIfNotEmptyOrElseSecond(List first, List second) { - final List result; - if (first != null && !first.isEmpty()) { - result = first; - } else { - result = second; - } - return result; - } } From b3d77a670489f8e4019b400e1da897de065b69c6 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 12:58:44 +0200 Subject: [PATCH 09/21] Bug 5049: introduced path parameters for /role REST endpoints --- java/com.sap.sse.security/resources/shiro.ini | 1 + .../com/sap/sse/security/jaxrs/api/RoleResource.java | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index 6e47e904c3c..cb807446cc5 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -73,3 +73,4 @@ bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter /api/restsecurity/remove_access_token = bearerToken /api/restsecurity/hello = bearerToken /api/restsecurity/role = bearerToken +/api/restsecurity/role/** = bearerToken diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index b9825c215de..30db849c0c2 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -7,10 +7,10 @@ import java.util.UUID; import javax.ws.rs.FormParam; import javax.ws.rs.GET; -import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -27,9 +27,10 @@ import com.sap.sse.security.shared.WildcardPermission; @Path("/restsecurity/role") public class RoleResource extends AbstractSecurityResource { - @POST + @Path("{roleId}") + @PUT @Produces("text/plain;charset=UTF-8") - public Response updatePermissionsForRole(@Context UriInfo uriInfo, @FormParam("roleId") String roleId, + public Response updatePermissionsForRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId, @FormParam("permissions") List permissionStrings) { Response resp; @@ -85,8 +86,9 @@ public class RoleResource extends AbstractSecurityResource { } @GET + @Path("{roleId}") @Produces("application/json;charset=UTF-8") - public Response getRole(@Context UriInfo uriInfo, @QueryParam("roleId") String roleId) { + public Response getRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId) { Response resp; try { From e17658593d3f60068ad84eae80cd01666449c1db Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 13:13:08 +0200 Subject: [PATCH 10/21] Bug 5049: added REST endpoints to CREATE and DELETE role definitions --- .../sse/security/jaxrs/api/RoleResource.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 30db849c0c2..e984cb2d9e6 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -4,7 +4,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Callable; +import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.PUT; @@ -22,11 +24,70 @@ import org.json.simple.JSONObject; import com.sap.sse.security.jaxrs.AbstractSecurityResource; import com.sap.sse.security.shared.RoleDefinition; +import com.sap.sse.security.shared.TypeRelativeObjectIdentifier; import com.sap.sse.security.shared.WildcardPermission; +import com.sap.sse.security.shared.impl.SecuredSecurityTypes; @Path("/restsecurity/role") public class RoleResource extends AbstractSecurityResource { + @PUT + @Produces("application/json;charset=UTF-8") + public Response createRole(@Context UriInfo uriInfo, @FormParam("roleName") String roleName) { + final String roleDefinitionIdAsString = UUID.randomUUID().toString(); + final RoleDefinition role = getService().setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError( + SecuredSecurityTypes.ROLE_DEFINITION, new TypeRelativeObjectIdentifier(roleDefinitionIdAsString), + roleName, new Callable() { + @Override + public RoleDefinition call() throws Exception { + return getService().createRoleDefinition(UUID.fromString(roleDefinitionIdAsString), roleName); + } + }); + + final Response resp; + if (role == null) { + resp = Response.status(Status.INTERNAL_SERVER_ERROR).entity("Role creation failed.").build(); + } else { + final JSONObject jsonResult = new JSONObject(); + jsonResult.put("permissions", new JSONArray()); + jsonResult.put("id", role.getId().toString()); + jsonResult.put("name", role.getName()); + resp = Response.status(Status.CREATED).entity(jsonResult.toJSONString()).build(); + } + return resp; + } + + @Path("{roleId}") + @DELETE + @Produces("application/json;charset=UTF-8") + public Response deleteRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId) { + Response resp; + try { + + // parse UUID + final UUID roleUUID = UUID.fromString(roleId); + + // get role definition from role id + final RoleDefinition roleDefinition = getService().getRoleDefinition(roleUUID); + + // null check role definition + if (roleDefinition == null) { + resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) + .build(); + } else { + // check read permission on role + getService().checkCurrentUserDeletePermission(roleDefinition); + getService().deleteRoleDefinition(roleDefinition); + resp = Response.status(Status.NO_CONTENT).build(); + } + } catch (IllegalArgumentException e) { + resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); + } catch (UnauthorizedException e) { + resp = Response.status(Status.UNAUTHORIZED).build(); + } + return resp; + } + @Path("{roleId}") @PUT @Produces("text/plain;charset=UTF-8") From e38e0072bf7ae935702d6869e5ebd0bbe010cec7 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 14:09:03 +0200 Subject: [PATCH 11/21] Bug 5049: changed HTTP PUT to HTTP POST for creation of roles by name --- .../src/com/sap/sse/security/jaxrs/api/RoleResource.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index e984cb2d9e6..ffb26c49fa0 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -9,6 +9,7 @@ import java.util.concurrent.Callable; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -31,7 +32,7 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes; @Path("/restsecurity/role") public class RoleResource extends AbstractSecurityResource { - @PUT + @POST @Produces("application/json;charset=UTF-8") public Response createRole(@Context UriInfo uriInfo, @FormParam("roleName") String roleName) { final String roleDefinitionIdAsString = UUID.randomUUID().toString(); From aca242568dcd04818b8004b1dad606c7ef8bae46 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 15:48:45 +0200 Subject: [PATCH 12/21] Bug 5049: Adjusted RoleResource to work with JSONObjects in body when HTTP PUT is used --- .../sse/security/jaxrs/api/RoleResource.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index ffb26c49fa0..0770e2f386e 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -1,11 +1,11 @@ package com.sap.sse.security.jaxrs.api; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -15,6 +15,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -22,6 +23,8 @@ import javax.ws.rs.core.UriInfo; import org.apache.shiro.authz.UnauthorizedException; import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import com.sap.sse.security.jaxrs.AbstractSecurityResource; import com.sap.sse.security.shared.RoleDefinition; @@ -92,8 +95,8 @@ public class RoleResource extends AbstractSecurityResource { @Path("{roleId}") @PUT @Produces("text/plain;charset=UTF-8") - public Response updatePermissionsForRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId, - @FormParam("permissions") List permissionStrings) { + @Consumes(MediaType.APPLICATION_JSON) + public Response updateRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId, String json) { Response resp; try { @@ -114,6 +117,10 @@ public class RoleResource extends AbstractSecurityResource { // create permission objects final Set permissions = new HashSet<>(); + + final JSONObject body = (JSONObject) new JSONParser().parse(json); + @SuppressWarnings("unchecked") + final Iterable permissionStrings = (Iterable) body.get("permissions"); for (final String permissionString : permissionStrings) { permissions.add(new WildcardPermission(permissionString)); } @@ -135,11 +142,15 @@ public class RoleResource extends AbstractSecurityResource { } else { // update role definitino with new permissions roleDefinition.setPermissions(permissions); + final String roleName = (String) body.get("roleName"); + if (roleName != null) { + roleDefinition.setName(roleName); + } getService().updateRoleDefinition(roleDefinition); resp = Response.ok().build(); } } - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | ParseException e) { resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build(); } catch (UnauthorizedException e) { resp = Response.status(Status.UNAUTHORIZED).build(); From 61884268b2d445d07c19c3e10b05a522e454e9e4 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 15:49:06 +0200 Subject: [PATCH 13/21] Bug 5049: added selenlium test for RoleAPI --- .../sailing/selenium/api/event/RoleApi.java | 67 +++++++++++ .../selenium/api/test/RoleApiTest.java | 110 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java create mode 100644 java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java new file mode 100644 index 00000000000..0b9df18264e --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java @@ -0,0 +1,67 @@ +package com.sap.sailing.selenium.api.event; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.selenium.api.core.ApiContext; +import com.sap.sailing.selenium.api.core.JsonWrapper; + +public class RoleApi { + + private static final String ROLE_URL = "/api/restsecurity/role/"; + + public Role getRole(ApiContext ctx, UUID roleId) { + return new Role(ctx.get(ROLE_URL + roleId.toString())); + } + + public Role createRole(ApiContext ctx, String roleName) { + final Map formParams = new HashMap<>(); + formParams.put("roleName", roleName); + return new Role(ctx.post(ROLE_URL, new HashMap<>(), formParams)); + } + + public void deleteRole(ApiContext ctx, UUID roleId) { + ctx.delete(ROLE_URL + roleId.toString()); + } + + public String updateRole(ApiContext ctx, UUID roleId, Iterable permissionStrings, String roleName) { + final JSONObject json = new JSONObject(); + json.put("roleName", roleName); + final JSONArray permissionArray = new JSONArray(); + for (String permission : permissionStrings) { + permissionArray.add(permission); + } + json.put("permissions", permissionArray); + return ctx.put(ROLE_URL + roleId.toString(), new HashMap<>(), json); + } + + public class Role extends JsonWrapper { + + public Role(JSONObject json) { + super(json); + } + + public String getName() { + return get("name"); + } + + public UUID getId() { + return UUID.fromString(get("id")); + } + + public Iterable getPermissions() { + JSONArray array = get("permissions"); + Collection col = new ArrayList<>(); + for (Object w : array) { + col.add(w.toString()); + } + return col; + } + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java new file mode 100644 index 00000000000..5659d6b8111 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java @@ -0,0 +1,110 @@ +package com.sap.sailing.selenium.api.test; + +import static com.sap.sailing.selenium.api.core.ApiContext.SECURITY_CONTEXT; +import static com.sap.sailing.selenium.api.core.ApiContext.createAdminApiContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.selenium.api.core.ApiContext; +import com.sap.sailing.selenium.api.event.RoleApi; +import com.sap.sailing.selenium.api.event.RoleApi.Role; +import com.sap.sailing.selenium.test.AbstractSeleniumTest; +import com.sap.sse.common.Util; + +public class RoleApiTest extends AbstractSeleniumTest { + + private final RoleApi roleApi = new RoleApi(); + + @Before + public void setUp() { + clearState(getContextRoot()); + } + + @Test + public void testCreateAndGetRole() { + final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT); + + final String testRoleName = "test role 123"; + final Role roleCreated = roleApi.createRole(adminCtx, testRoleName); + + assertEquals("Responded role name of createRole is different!", testRoleName, roleCreated.getName()); + assertNotNull("UUID cannot be null!", roleCreated.getId()); + assertTrue("Permissions should be empty!", Util.isEmpty(roleCreated.getPermissions())); + + final Role roleGet = roleApi.getRole(adminCtx, roleCreated.getId()); + + assertEquals("Responded role name of getRole is different!", roleCreated.getName(), roleGet.getName()); + assertEquals("UUID must be the same!", roleCreated.getId(), roleGet.getId()); + assertEquals("Permissions changed!", roleCreated.getPermissions(), roleGet.getPermissions()); + + roleApi.deleteRole(adminCtx, roleCreated.getId()); + } + + @Test + public void testDeleteRole() { + final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT); + + final String testRoleName = "test role 123"; + final Role roleCreated = roleApi.createRole(adminCtx, testRoleName); + + assertNotNull("UUID cannot be null!", roleCreated.getId()); + + final Role roleGet = roleApi.getRole(adminCtx, roleCreated.getId()); + + assertEquals("UUID must be the same!", roleCreated.getId(), roleGet.getId()); + + roleApi.deleteRole(adminCtx, roleCreated.getId()); + + try { + roleApi.getRole(adminCtx, roleCreated.getId()); + fail("Expected exception since role should not be parsable!"); + } catch (RuntimeException e) { + assertTrue("Unrelated error", e.getMessage().contains("No role with id")); + } + } + + @Test + public void testUpdateRole() { + final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT); + + final String testRoleName = "test role 123"; + final Role roleCreated = roleApi.createRole(adminCtx, testRoleName); + + assertNotNull("UUID cannot be null!", roleCreated.getId()); + + final Role roleGet = roleApi.getRole(adminCtx, roleCreated.getId()); + + assertEquals("UUID must be the same!", roleCreated.getId(), roleGet.getId()); + + final Collection permissions = new ArrayList<>(); + permissions.add("COMPETITOR,EVENT:READ"); + permissions.add("BOAT:READ"); + + final String updatedRoleName = roleCreated.getName() + "_changed"; + final String roleUpdate = roleApi.updateRole(adminCtx, roleCreated.getId(), permissions, updatedRoleName); + + assertNull("Empty string expected.", roleUpdate); + + final Role roleGetAfterUpdate = roleApi.getRole(adminCtx, roleCreated.getId()); + + assertEquals("Responded role name of getRole is different!", updatedRoleName, roleGetAfterUpdate.getName()); + assertEquals("UUID must be the same!", roleCreated.getId(), roleGetAfterUpdate.getId()); + + // |A| = |B| ∧ ∀a∈ A: a∈ B --> A and B have the same elements, ignoring order + assertEquals("Permissions changed!", permissions.size(), Util.size(roleGetAfterUpdate.getPermissions())); + for (String permission : roleGetAfterUpdate.getPermissions()) { + assertTrue("Permissions missing!", permission.contains(permission)); + } + + roleApi.deleteRole(adminCtx, roleCreated.getId()); + } +} From 4779d1a519a15e586cc106420cb3ed9655996c4a Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 16:09:15 +0200 Subject: [PATCH 14/21] Bug 5049: extracted constants for roleId, roleName and permissions --- .../sailing/selenium/api/event/RoleApi.java | 15 ++++++---- .../sse/security/jaxrs/api/RoleResource.java | 28 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java index 0b9df18264e..21bdd9b5d1c 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java @@ -15,6 +15,9 @@ import com.sap.sailing.selenium.api.core.JsonWrapper; public class RoleApi { private static final String ROLE_URL = "/api/restsecurity/role/"; + private static final String KEY_ROLE_NAME = "roleName"; + private static final String KEY_PERMISSIONS = "permissions"; + private static final String KEY_ID = "roleId"; public Role getRole(ApiContext ctx, UUID roleId) { return new Role(ctx.get(ROLE_URL + roleId.toString())); @@ -22,7 +25,7 @@ public class RoleApi { public Role createRole(ApiContext ctx, String roleName) { final Map formParams = new HashMap<>(); - formParams.put("roleName", roleName); + formParams.put(KEY_ROLE_NAME, roleName); return new Role(ctx.post(ROLE_URL, new HashMap<>(), formParams)); } @@ -32,12 +35,12 @@ public class RoleApi { public String updateRole(ApiContext ctx, UUID roleId, Iterable permissionStrings, String roleName) { final JSONObject json = new JSONObject(); - json.put("roleName", roleName); + json.put(KEY_ROLE_NAME, roleName); final JSONArray permissionArray = new JSONArray(); for (String permission : permissionStrings) { permissionArray.add(permission); } - json.put("permissions", permissionArray); + json.put(KEY_PERMISSIONS, permissionArray); return ctx.put(ROLE_URL + roleId.toString(), new HashMap<>(), json); } @@ -48,15 +51,15 @@ public class RoleApi { } public String getName() { - return get("name"); + return get(KEY_ROLE_NAME); } public UUID getId() { - return UUID.fromString(get("id")); + return UUID.fromString(get(KEY_ID)); } public Iterable getPermissions() { - JSONArray array = get("permissions"); + JSONArray array = get(KEY_PERMISSIONS); Collection col = new ArrayList<>(); for (Object w : array) { col.add(w.toString()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 0770e2f386e..99863919b27 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -35,9 +35,13 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes; @Path("/restsecurity/role") public class RoleResource extends AbstractSecurityResource { + private static final String KEY_PERMISSIONS = "permissions"; + private static final String KEY_ROLE_ID = "roleId"; + private static final String KEY_ROLE_NAME = "roleName"; + @POST @Produces("application/json;charset=UTF-8") - public Response createRole(@Context UriInfo uriInfo, @FormParam("roleName") String roleName) { + public Response createRole(@Context UriInfo uriInfo, @FormParam(KEY_ROLE_NAME) String roleName) { final String roleDefinitionIdAsString = UUID.randomUUID().toString(); final RoleDefinition role = getService().setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError( SecuredSecurityTypes.ROLE_DEFINITION, new TypeRelativeObjectIdentifier(roleDefinitionIdAsString), @@ -53,9 +57,9 @@ public class RoleResource extends AbstractSecurityResource { resp = Response.status(Status.INTERNAL_SERVER_ERROR).entity("Role creation failed.").build(); } else { final JSONObject jsonResult = new JSONObject(); - jsonResult.put("permissions", new JSONArray()); - jsonResult.put("id", role.getId().toString()); - jsonResult.put("name", role.getName()); + jsonResult.put(KEY_PERMISSIONS, new JSONArray()); + jsonResult.put(KEY_ROLE_ID, role.getId().toString()); + jsonResult.put(KEY_ROLE_NAME, role.getName()); resp = Response.status(Status.CREATED).entity(jsonResult.toJSONString()).build(); } return resp; @@ -64,7 +68,7 @@ public class RoleResource extends AbstractSecurityResource { @Path("{roleId}") @DELETE @Produces("application/json;charset=UTF-8") - public Response deleteRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId) { + public Response deleteRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) String roleId) { Response resp; try { @@ -96,7 +100,7 @@ public class RoleResource extends AbstractSecurityResource { @PUT @Produces("text/plain;charset=UTF-8") @Consumes(MediaType.APPLICATION_JSON) - public Response updateRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId, String json) { + public Response updateRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) String roleId, String json) { Response resp; try { @@ -120,7 +124,7 @@ public class RoleResource extends AbstractSecurityResource { final JSONObject body = (JSONObject) new JSONParser().parse(json); @SuppressWarnings("unchecked") - final Iterable permissionStrings = (Iterable) body.get("permissions"); + final Iterable permissionStrings = (Iterable) body.get(KEY_PERMISSIONS); for (final String permissionString : permissionStrings) { permissions.add(new WildcardPermission(permissionString)); } @@ -142,7 +146,7 @@ public class RoleResource extends AbstractSecurityResource { } else { // update role definitino with new permissions roleDefinition.setPermissions(permissions); - final String roleName = (String) body.get("roleName"); + final String roleName = (String) body.get(KEY_ROLE_NAME); if (roleName != null) { roleDefinition.setName(roleName); } @@ -161,7 +165,7 @@ public class RoleResource extends AbstractSecurityResource { @GET @Path("{roleId}") @Produces("application/json;charset=UTF-8") - public Response getRole(@Context UriInfo uriInfo, @PathParam("roleId") String roleId) { + public Response getRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) String roleId) { Response resp; try { @@ -186,9 +190,9 @@ public class RoleResource extends AbstractSecurityResource { for (final WildcardPermission permission : roleDefinition.getPermissions()) { jsonPermissions.add(permission.toString()); } - jsonResult.put("permissions", jsonPermissions); - jsonResult.put("id", roleId); - jsonResult.put("name", roleDefinition.getName()); + jsonResult.put(KEY_PERMISSIONS, jsonPermissions); + jsonResult.put(KEY_ROLE_ID, roleId); + jsonResult.put(KEY_ROLE_NAME, roleDefinition.getName()); resp = Response.ok(jsonResult.toJSONString()).build(); } } catch (IllegalArgumentException e) { From 5d79885d0da5ea4f39f7d74fc3e9802657150034 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 17:20:05 +0200 Subject: [PATCH 15/21] Bug 5049: adjusted documentation of REST endpoints related to roles according to changes in the previous commits. --- .../sse/security/jaxrs/api/RoleResource.java | 2 +- .../webservices/api/role.html | 86 ++++++++++++++++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 99863919b27..600e3344dc1 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -67,7 +67,7 @@ public class RoleResource extends AbstractSecurityResource { @Path("{roleId}") @DELETE - @Produces("application/json;charset=UTF-8") + @Produces("text/plain;charset=UTF-8") public Response deleteRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) String roleId) { Response resp; try { diff --git a/java/com.sap.sse.security/webservices/api/role.html b/java/com.sap.sse.security/webservices/api/role.html index 6c2ebcb09e5..024ebe93112 100644 --- a/java/com.sap.sse.security/webservices/api/role.html +++ b/java/com.sap.sse.security/webservices/api/role.html @@ -12,12 +12,16 @@

URL: /api/restsecurity/role

Description: -

The service reads and updates a role and the associated permissions based on the HTTP method used.

+

The service can get, create, delete and update a role and based on the HTTP method used. The path parameter roleId determines, which role is deleted/updated/retrieved.


- + + + + + @@ -26,36 +30,98 @@ - + -
Request Method:GETGET /{roleId}
Description:Retrieve the role with the associated roleId.
Output format:
Mandatory parameters:Mandatory path parameters:
roleId: UUID of the role to get.
Example:curl -X GET "127.0.0.1:8888/security/api/restsecurity/role?roleId=29914406-b0fc-43cd-acc7-62a83931f587"
+
curl -X GET "127.0.0.1:8888/security/api/restsecurity/role/29914406-b0fc-43cd-acc7-62a83931f587"
Will provide a JSON document as explained above.
+ + + + + + + + + + + + + + + + + + + + + + + + +
Request Method:PUT /{roleId}
Description:Update the role with the associated roleId with the parameters in the JSON body.
Output format:An empty response with status code 200 in case of success; error-indicating status codes otherwise
Mandatory path parameters: +
roleId: UUID of the role to get.
+
Parameters in the JSON body: +
Mandatory: permissions: Set of String permissions, the role's current permissions will be replaced with.
+
Optional: roleName: the role's current role name will be replaced with this.
+
Example:curl --header "Content-Type: application/json" --request PUT --data '{"permissions": ["COMPETITOR,EVENT:READ", "BOAT:READ"], "roleName":"testrole2"}' 127.0.0.1:8888/security/api/restsecurity/role/29914406-b0fc-43cd-acc7-62a83931f587
+
+ + + + + + + + + + + + + + + + + + + + + +
Request Method:DELETE /{roleId}
Description:Delete the role with the associated roleId.
Output format:a JSON document of the following format
+
{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}
+
Mandatory path parameters: +
roleId: UUID of the role to get.
+
Example:curl -X DELETE "127.0.0.1:8888/security/api/restsecurity/role/29914406-b0fc-43cd-acc7-62a83931f587"
- - + - + + + + + -
Request Method: POST
Output format:An empty response with status code 200 in case of success; error-indicating status codes otherwise + Description:Create a role with the roleName given as a form parameter.
Mandatory parameters:Output format:a JSON document of the following format
+
{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}
+
Mandatory form parameters: -
roleId: UUID of the role to update.
-
permissions: Set of String permissions, the role's current permissions will be replaced with.
+
roleName: Name of the role to create.
Example:curl -X POST "127.0.0.1:8888/security/api/restsecurity/role?roleId=29914406-b0fc-43cd-acc7-62a83931f587&permissions=BOAT:READ&permissions=COMPETITOR,EVENT:READ"
+
curl -X POST "127.0.0.1:8888/security/api/restsecurity/role/?roleName=test-role"
From 3711762256c9a1687e7966626a2d49f9f76b9e75 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 17:26:49 +0200 Subject: [PATCH 16/21] Bug 5049: adjusted deletion of roles to avoid orphaned ownerships. --- .../src/com/sap/sse/security/jaxrs/api/RoleResource.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java index 600e3344dc1..f8874011529 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/RoleResource.java @@ -83,9 +83,10 @@ public class RoleResource extends AbstractSecurityResource { resp = Response.status(Status.NOT_FOUND).entity(String.format("No role with id '%s' found.", roleUUID)) .build(); } else { - // check read permission on role - getService().checkCurrentUserDeletePermission(roleDefinition); - getService().deleteRoleDefinition(roleDefinition); + // check delete permission and remove role + getService().checkPermissionAndDeleteOwnershipForObjectRemoval(roleDefinition, () -> { + getService().deleteRoleDefinition(roleDefinition); + }); resp = Response.status(Status.NO_CONTENT).build(); } } catch (IllegalArgumentException e) { From cc47598ab22d4b77b721d5b1268b2fc2b2a624d8 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 19:30:28 +0200 Subject: [PATCH 17/21] Bug 5049: fixed bug in RoleApiTest which resulted in the permissions associated to a role not being checked properly --- .../src/com/sap/sailing/selenium/api/test/RoleApiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java index 5659d6b8111..b01f66021b0 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/test/RoleApiTest.java @@ -102,7 +102,7 @@ public class RoleApiTest extends AbstractSeleniumTest { // |A| = |B| ∧ ∀a∈ A: a∈ B --> A and B have the same elements, ignoring order assertEquals("Permissions changed!", permissions.size(), Util.size(roleGetAfterUpdate.getPermissions())); for (String permission : roleGetAfterUpdate.getPermissions()) { - assertTrue("Permissions missing!", permission.contains(permission)); + assertTrue("Permissions missing!", permissions.contains(permission)); } roleApi.deleteRole(adminCtx, roleCreated.getId()); From 4bab1dcca45b9e845553e876f8040ebab9f35231 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 28 May 2019 19:30:57 +0200 Subject: [PATCH 18/21] Bug 5049: fixed potential NullPointerException if parsing roles from JSON and the permissions are empty --- .../src/com/sap/sailing/selenium/api/event/RoleApi.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java index 21bdd9b5d1c..d6d04a43ee4 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/api/event/RoleApi.java @@ -61,8 +61,10 @@ public class RoleApi { public Iterable getPermissions() { JSONArray array = get(KEY_PERMISSIONS); Collection col = new ArrayList<>(); - for (Object w : array) { - col.add(w.toString()); + if (array != null) { + for (Object w : array) { + col.add(w.toString()); + } } return col; } From 2997dbb790918706c4649d2ebcc25a2253e8c2d0 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 24 Jun 2019 11:17:39 +0200 Subject: [PATCH 19/21] Bug 5049: added rule.html to index.html --- java/com.sap.sse.security/webservices/api/index.html | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sse.security/webservices/api/index.html b/java/com.sap.sse.security/webservices/api/index.html index 4e53b2fecea..a1eba6a7817 100755 --- a/java/com.sap.sse.security/webservices/api/index.html +++ b/java/com.sap.sse.security/webservices/api/index.html @@ -38,10 +38,14 @@ be http://sapsailing.com/security/api/restsecurity/login. /api/restsecurity/forgot_password Trigger the process of password recovery - - /api/restsecurity/user - Read, update or delete a user - + + /api/restsecurity/user + Read, update or delete a user + + + /api/restsecurity/role + Create, read, update or delete a role + /api/restsecurity/access_token Obtain a bearer access token From ae8200d9af09b46baa34b452ae3ac4db69c01c0d Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Mon, 24 Jun 2019 11:21:00 +0200 Subject: [PATCH 20/21] Bug 5049: added role api endpoint changes to release notes --- java/com.sap.sailing.www/release_notes_admin.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index cafc0699e99..1428e825328 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -21,6 +21,11 @@

Release Notes - Administration Console

+

June 2019

+
    +
  • Added new API endpoints for creating, reading, updating and deleting roles under /restsecurity/role. +
  • +

May 2019

  • Ranks (finishing orders) entered by the Race Manager app user are no longer From e43c6090e50f765dec0310643002f273543240b2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 24 Jun 2019 17:10:14 +0200 Subject: [PATCH 21/21] bug5076: avoid NPE during TrackedRaceImpl.getAdditionalGateStartDistance if competitor or start line position is unknown Change-Id: Ie40c8b141c0daeb1950eceaecd0dbf4002e123c6 --- .../sap/sailing/domain/tracking/impl/TrackedRaceImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 239fa7dfc9e..32d38f3f2e4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -3522,7 +3522,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl final Mark portMarkOfStartLine = getStartLine(competitorLegStartTime).getPortMarkWhileApproachingLine(); final Position portSideOfStartLinePosition = getOrCreateTrack(portMarkOfStartLine) .getEstimatedPosition(competitorLegStartTime, /* extrapolate */true); - result = portSideOfStartLinePosition.getDistance(getTrack(competitor).getEstimatedPosition(competitorLegStartTime, /* extrapolate */false)); + final Position estimatedCompetitorPositionAtStart = getTrack(competitor).getEstimatedPosition(competitorLegStartTime, /* extrapolate */false); + if (estimatedCompetitorPositionAtStart != null && portSideOfStartLinePosition != null) { + result = portSideOfStartLinePosition.getDistance(estimatedCompetitorPositionAtStart); + } else { + result = Distance.NULL; + } } else { result = Distance.NULL; }