Merge branch 'master' into bug2842

This commit is contained in:
Daniel Lisunkin committed 2019-06-24 18:39:18 +02:00
commit 12b93c00fa
10 files changed
+546 -5

No files matched your search

@@ -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;
}
@@ -0,0 +1,72 @@
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/";
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()));
}
public Role createRole(ApiContext ctx, String roleName) {
final Map<String, String> formParams = new HashMap<>();
formParams.put(KEY_ROLE_NAME, 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<String> permissionStrings, String roleName) {
final JSONObject json = new JSONObject();
json.put(KEY_ROLE_NAME, roleName);
final JSONArray permissionArray = new JSONArray();
for (String permission : permissionStrings) {
permissionArray.add(permission);
}
json.put(KEY_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(KEY_ROLE_NAME);
}
public UUID getId() {
return UUID.fromString(get(KEY_ID));
}
public Iterable<String> getPermissions() {
JSONArray array = get(KEY_PERMISSIONS);
Collection<String> col = new ArrayList<>();
if (array != null) {
for (Object w : array) {
col.add(w.toString());
}
}
return col;
}
}
}
@@ -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<String> 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!", permissions.contains(permission));
}
roleApi.deleteRole(adminCtx, roleCreated.getId());
}
}
@@ -21,6 +21,11 @@
<div class="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">June 2019</h2>
<ul class="bulletList">
<li>Added new API endpoints for creating, reading, updating and deleting roles under /restsecurity/role.
</li>
</ul>
<h2 class="articleSubheadline">May 2019</h2>
<ul class="bulletList">
<li>Ranks (finishing orders) entered by the Race Manager app user are no longer
@@ -181,6 +181,14 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
+ roleDefinitionWithNewProperties.getName());
}
Set<WildcardPermission> 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);
}
@@ -72,3 +72,5 @@ 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
/api/restsecurity/role/** = bearerToken
@@ -11,6 +11,7 @@ public class RestApiApplication extends Application {
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<>();
classes.add(SecurityResource.class);
classes.add(RoleResource.class);
// exception mapper
classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class);
@@ -0,0 +1,206 @@
package com.sap.sse.security.jaxrs.api;
import java.util.HashSet;
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;
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.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;
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;
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 {
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(KEY_ROLE_NAME) String roleName) {
final String roleDefinitionIdAsString = UUID.randomUUID().toString();
final RoleDefinition role = getService().setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(
SecuredSecurityTypes.ROLE_DEFINITION, new TypeRelativeObjectIdentifier(roleDefinitionIdAsString),
roleName, new Callable<RoleDefinition>() {
@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(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;
}
@Path("{roleId}")
@DELETE
@Produces("text/plain;charset=UTF-8")
public Response deleteRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) 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 delete permission and remove role
getService().checkPermissionAndDeleteOwnershipForObjectRemoval(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")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) String roleId, String json) {
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<WildcardPermission> permissions = new HashSet<>();
final JSONObject body = (JSONObject) new JSONParser().parse(json);
@SuppressWarnings("unchecked")
final Iterable<String> permissionStrings = (Iterable<String>) body.get(KEY_PERMISSIONS);
for (final String permissionString : permissionStrings) {
permissions.add(new WildcardPermission(permissionString));
}
// check only those meta-permissions which changed
final Set<WildcardPermission> addedPermissions = new HashSet<>(roleDefinition.getPermissions());
addedPermissions.removeAll(permissions);
final Set<WildcardPermission> 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);
final String roleName = (String) body.get(KEY_ROLE_NAME);
if (roleName != null) {
roleDefinition.setName(roleName);
}
getService().updateRoleDefinition(roleDefinition);
resp = Response.ok().build();
}
}
} catch (IllegalArgumentException | ParseException e) {
resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build();
} catch (UnauthorizedException e) {
resp = Response.status(Status.UNAUTHORIZED).build();
}
return resp;
}
@GET
@Path("{roleId}")
@Produces("application/json;charset=UTF-8")
public Response getRole(@Context UriInfo uriInfo, @PathParam(KEY_ROLE_ID) 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().checkCurrentUserReadPermission(roleDefinition);
// build json result with permissions and id
final JSONObject jsonResult = new JSONObject();
final JSONArray jsonPermissions = new JSONArray();
for (final WildcardPermission permission : roleDefinition.getPermissions()) {
jsonPermissions.add(permission.toString());
}
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) {
resp = Response.status(Status.BAD_REQUEST).entity("Invalid roleId.").build();
} catch (UnauthorizedException e) {
resp = Response.status(Status.UNAUTHORIZED).build();
}
return resp;
}
}
@@ -38,10 +38,14 @@ be <code>http://sapsailing.com/security/api/restsecurity/login</code>.
<td><a href="user.html">/api/restsecurity/forgot_password</a></td>
<td>Trigger the process of password recovery</td>
</tr>
<tr>
<td><a href="user.html">/api/restsecurity/user</a></td>
<td>Read, update or delete a user</td>
</tr>
<tr>
<td><a href="user.html">/api/restsecurity/user</a></td>
<td>Read, update or delete a user</td>
</tr>
<tr>
<td><a href="role.html">/api/restsecurity/role</a></td>
<td>Create, read, update or delete a role</td>
</tr>
<tr>
<td><a href="access_token.html">/api/restsecurity/access_token</a></td>
<td>Obtain a bearer access token</td>
@@ -0,0 +1,128 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link href='../../../../fontface.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
<title>SAP Sponsorships Engine Security Webservices API</title>
</head>
<body>
<h1>SAP Sponsorships Engine Security Webservices API</h1>
<h2>URL: /api/restsecurity/role</h2>
<b>Description:</b>
<p>The service can get, create, delete and update a role and based on the HTTP method used. The path parameter <i>roleId</i> determines, which role is deleted/updated/retrieved.</p>
<br/>
<table>
<tr>
<td>Request Method:</td>
<td>GET /{roleId}</td>
</tr>
<tr>
<td>Description:</td>
<td>Retrieve the role with the associated <i>roleId</i>.</td>
</tr>
<tr>
<td>Output format:</td>
<td>a JSON document of the following format<br>
<pre>{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}</pre>
</td>
</tr>
<tr>
<td>Mandatory path parameters:</td>
<td>
<div>roleId: UUID of the role to get.</div>
</td>
</tr>
<tr>
<td>Example:</td>
<td><code>curl -X GET "127.0.0.1:8888/security/api/restsecurity/role/29914406-b0fc-43cd-acc7-62a83931f587"</code><br>
Will provide a JSON document as explained above.</td>
</tr>
</table>
<table>
<tr>
<td>Request Method:</td>
<td>PUT /{roleId}</td>
</tr>
<tr>
<td>Description:</td>
<td>Update the role with the associated <i>roleId</i> with the parameters in the JSON body.</td>
</tr>
<tr>
<td>Output format:</td>
<td>An empty response with status code 200 in case of success; error-indicating status codes otherwise</td>
</tr>
<tr>
<td>Mandatory path parameters:</td>
<td>
<div>roleId: UUID of the role to get.</div>
</td>
</tr>
<tr>
<td>Parameters in the JSON body:</td>
<td>
<div>Mandatory: permissions: Set of String permissions, the role's current permissions will be replaced with.</div>
<div>Optional: roleName: the role's current role name will be replaced with this.</div>
</td>
</tr>
<tr>
<td>Example:</td>
<td><code>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</code><br>
</tr>
</table>
<table>
<tr>
<td>Request Method:</td>
<td>DELETE /{roleId}</td>
</tr>
<tr>
<td>Description:</td>
<td>Delete the role with the associated <i>roleId</i>.</td>
</tr>
<tr>
<td>Output format:</td>
<td>a JSON document of the following format<br>
<pre>{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}</pre>
</td>
</tr>
<tr>
<td>Mandatory path parameters:</td>
<td>
<div>roleId: UUID of the role to get.</div>
</td>
</tr>
<tr>
<td>Example:</td>
<td><code>curl -X DELETE "127.0.0.1:8888/security/api/restsecurity/role/29914406-b0fc-43cd-acc7-62a83931f587"</code></td>
</tr>
</table>
<table>
<tr>
<td>Request Method:</td>
<td>POST</td>
</tr>
<tr>
<td>Description:</td>
<td>Create a role with the <i>roleName</i> given as a form parameter.</td>
</tr>
<tr>
<td>Output format:</td>
<td>a JSON document of the following format<br>
<pre>{"permissions": ["BOAT:READ", "COMPETITOR,EVENT:READ"], "id": "29914406-b0fc-43cd-acc7-62a83931f587", "name": "test-role"}</pre>
</td>
</tr>
<tr>
<td>Mandatory form parameters:</td>
<td>
<div>roleName: Name of the role to create.</div>
</td>
</tr>
<tr>
<td>Example:</td>
<td><code>curl -X POST "127.0.0.1:8888/security/api/restsecurity/role/?roleName=test-role"</code></td>
</tr>
</table>
</body>
</html>