diff --git a/pom.xml b/pom.xml
index 6a9de758..9f04772a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
1.12.2
5.11.2
- 10.1.0
+ 10.3.0
0.5.10
${project.version}
diff --git a/src/main/java/com/uid2/admin/vertx/Endpoints.java b/src/main/java/com/uid2/admin/vertx/Endpoints.java
index 0bedc579..f8089ebb 100644
--- a/src/main/java/com/uid2/admin/vertx/Endpoints.java
+++ b/src/main/java/com/uid2/admin/vertx/Endpoints.java
@@ -82,6 +82,7 @@ public enum Endpoints {
API_SERVICE_ADD("/api/service/add"),
API_SERVICE_UPDATE("/api/service/update"),
API_SERVICE_DELETE("/api/service/delete"),
+ API_SERVICE_REMOVE_LINK_ID_REGEX("/api/service/remove-link-id-regex"),
API_SHARING_LISTS("/api/sharing/lists"),
API_SHARING_LIST_SITEID("/api/sharing/list/:siteId"),
diff --git a/src/main/java/com/uid2/admin/vertx/service/ServiceService.java b/src/main/java/com/uid2/admin/vertx/service/ServiceService.java
index 80885a02..9d19cd1c 100644
--- a/src/main/java/com/uid2/admin/vertx/service/ServiceService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/ServiceService.java
@@ -20,6 +20,8 @@
import org.slf4j.LoggerFactory;
import java.util.*;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import static com.uid2.admin.vertx.Endpoints.*;
@@ -67,6 +69,11 @@ public void setupRoutes(Router router) {
this.handleDelete(ctx);
}
}, new AuditParams(Collections.emptyList(), List.of("service_id")), Role.SUPER_USER));
+ router.post(API_SERVICE_REMOVE_LINK_ID_REGEX.toString()).blockingHandler(auth.handle((ctx) -> {
+ synchronized (writeLock) {
+ this.handleRemoveLinkIdRegex(ctx);
+ }
+ }, new AuditParams(Collections.emptyList(), List.of("service_id")), Role.PRIVILEGED));
}
private void handleServiceListAll(RoutingContext rc) {
@@ -113,6 +120,7 @@ private void handleServiceAdd(RoutingContext rc) {
Integer siteId = body.getInteger("site_id");
String name = body.getString("name");
JsonArray rolesSpec = body.getJsonArray("roles");
+ String linkIdRegex = body.getString("link_id_regex");
if (siteId == null || name == null || rolesSpec == null || rolesSpec.isEmpty()) {
ResponseUtil.error(rc, 400, "required parameters: site_id, name, roles");
return;
@@ -147,11 +155,20 @@ private void handleServiceAdd(RoutingContext rc) {
return;
}
+ if (linkIdRegex != null && !linkIdRegex.isBlank()) {
+ if (!isValidRegex(linkIdRegex)) {
+ ResponseUtil.error(rc, 400, "invalid parameter: link_id_regex; not a valid regex");
+ return;
+ }
+ } else {
+ linkIdRegex = null;
+ }
+
final List services = this.serviceProvider.getAllServices()
.stream().sorted(Comparator.comparingInt(Service::getServiceId))
.collect(Collectors.toList());
final int serviceId = 1 + services.stream().mapToInt(Service::getServiceId).max().orElse(0);
- Service service = new Service(serviceId, siteId, name, roles);
+ Service service = new Service(serviceId, siteId, name, roles, linkIdRegex);
services.add(service);
@@ -163,17 +180,17 @@ private void handleServiceAdd(RoutingContext rc) {
}
}
- // Can update the site_id, name and roles
+ // Can update the site_id, name, roles, and link_id_regex
private void handleUpdate(RoutingContext rc) {
try {
+ serviceProvider.loadContent();
+ Service service = findServiceFromRequest(rc);
+ if (service == null) return; // error already handled
+
JsonObject body = rc.body().asJsonObject();
- if (body == null) {
- ResponseUtil.error(rc, 400, "json payload required but not provided");
- return;
- }
- Integer serviceId = body.getInteger("service_id");
Integer siteId = body.getInteger("site_id");
String name = body.getString("name");
+ String linkIdRegex = body.getString("link_id_regex");
JsonArray rolesSpec = null;
if (body.getString("roles") != null && !body.getString("roles").isEmpty()) {
@@ -185,16 +202,7 @@ private void handleUpdate(RoutingContext rc) {
}
}
- if (serviceId == null) {
- ResponseUtil.error(rc, 400, "required parameters: service_id");
- return;
- }
-
- final Service service = serviceProvider.getService(serviceId);
- if (service == null) {
- ResponseUtil.error(rc, 404, "failed to find a service for service_id: " + serviceId);
- return;
- }
+ int serviceId = service.getServiceId();
// check that this does not create a duplicate service
if (siteHasService(siteId, name, serviceId)) {
@@ -226,6 +234,14 @@ private void handleUpdate(RoutingContext rc) {
service.setRoles(roles);
}
+ if (linkIdRegex != null && !linkIdRegex.isBlank()) {
+ if (!isValidRegex(linkIdRegex)) {
+ ResponseUtil.error(rc, 400, "invalid parameter: link_id_regex; not a valid regex");
+ return;
+ }
+ service.setLinkIdRegex(linkIdRegex);
+ }
+
if (siteId != null && siteId != 0) {
service.setSiteId(siteId);
}
@@ -234,10 +250,7 @@ private void handleUpdate(RoutingContext rc) {
service.setName(name);
}
- final List services = this.serviceProvider.getAllServices()
- .stream().sorted(Comparator.comparingInt(Service::getServiceId))
- .collect(Collectors.toList());
-
+ List services = getSortedServices();
storeWriter.upload(services, null);
@@ -248,40 +261,31 @@ private void handleUpdate(RoutingContext rc) {
}
private void handleDelete(RoutingContext rc) {
- final int serviceId;
- JsonObject body = rc.body() != null ? rc.body().asJsonObject() : null;
- if (body == null) {
- ResponseUtil.error(rc, 400, "json payload required but not provided");
- return;
- }
- serviceId = body.getInteger("service_id", -1);
- if (serviceId == -1) {
- ResponseUtil.error(rc, 400, "required parameters: service_id");
- return;
- }
-
try {
serviceProvider.loadContent();
-
- Service service = serviceProvider.getService(serviceId);
- if (service == null) {
- ResponseUtil.error(rc, 404, "failed to find a service for service_id: " + serviceId);
- return;
- }
-
- final List services = this.serviceProvider.getAllServices()
- .stream().sorted(Comparator.comparingInt(Service::getServiceId))
- .collect(Collectors.toList());
-
+ Service service = findServiceFromRequest(rc);
+ if (service == null) return; // error already handled
+ List services = getSortedServices();
services.remove(service);
-
storeWriter.upload(services, null);
-
rc.response().end(toJson(service).encodePrettily());
} catch (Exception e) {
ResponseUtil.errorInternal(rc, "Internal Server Error", e);
}
+ }
+ private void handleRemoveLinkIdRegex(RoutingContext rc) {
+ try {
+ serviceProvider.loadContent();
+ Service service = findServiceFromRequest(rc);
+ if (service == null) return; // error already handled
+ service.setLinkIdRegex(null);
+ List services = getSortedServices();
+ storeWriter.upload(services, null);
+ rc.response().end(toJson(service).encodePrettily());
+ } catch (Exception e) {
+ ResponseUtil.errorInternal(rc, "Internal Server Error", e);
+ }
}
private JsonObject toJson(Service s) {
@@ -290,6 +294,7 @@ private JsonObject toJson(Service s) {
jsonObject.put("site_id", s.getSiteId());
jsonObject.put("name", s.getName());
jsonObject.put("roles", s.getRoles());
+ jsonObject.put("link_id_regex", s.getLinkIdRegex());
return jsonObject;
}
@@ -302,4 +307,42 @@ private boolean siteHasService(Integer siteId, String name, int serviceId) {
&& serviceProvider.getAllServices().stream().anyMatch(s -> s.getServiceId() != serviceId
&& s.getSiteId() == siteId && s.getName().equals(name));
}
+
+
+ private Service findServiceFromRequest(RoutingContext rc) {
+ JsonObject body = rc.body() != null ? rc.body().asJsonObject() : null;
+ if (body == null) {
+ ResponseUtil.error(rc, 400, "json payload required but not provided");
+ return null;
+ }
+
+ int serviceId = body.getInteger("service_id", -1);
+ if (serviceId == -1) {
+ ResponseUtil.error(rc, 400, "required parameters: service_id");
+ return null;
+ }
+
+ Service service = serviceProvider.getService(serviceId);
+ if (service == null) {
+ ResponseUtil.error(rc, 404, "failed to find a service for service_id: " + serviceId);
+ return null;
+ }
+ return service;
+ }
+
+ private List getSortedServices() {
+ return serviceProvider.getAllServices()
+ .stream()
+ .sorted(Comparator.comparingInt(Service::getServiceId))
+ .collect(Collectors.toList());
+ }
+
+ private boolean isValidRegex(String regex) {
+ try {
+ Pattern.compile(regex);
+ return true;
+ } catch (PatternSyntaxException e) {
+ return false;
+ }
+ }
}
diff --git a/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java b/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java
index fa8f975f..36f84260 100644
--- a/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java
+++ b/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java
@@ -50,7 +50,8 @@ private void checkServiceJson(Service expectedService, JsonObject actualService)
() -> {
Set actualRoles = actualService.getJsonArray("roles").stream().map(s -> Role.valueOf((String) s)).collect(Collectors.toSet());
assertEquals(expectedService.getRoles(), actualRoles);
- }
+ },
+ () -> assertEquals(expectedService.getLinkIdRegex(), actualService.getString("link_id_regex"))
);
}
@@ -273,6 +274,29 @@ void addServiceBadRoles(Vertx vertx, VertxTestContext testContext) {
});
}
+ @Test
+ void addServiceBadLinkIdRegex(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ setSites(new Site(123, "name1", false));
+
+ JsonArray ja = new JsonArray();
+ ja.add("GENERATOR");
+
+ JsonObject jo = new JsonObject();
+ jo.put("site_id", 123);
+ jo.put("name", "name1");
+ jo.put("roles", ja);
+ jo.put("link_id_regex", "[unclosed");
+
+ post(vertx, testContext, "api/service/add", jo.encode(), response -> {
+ assertEquals(400, response.statusCode());
+ assertEquals("invalid parameter: link_id_regex; not a valid regex", response.bodyAsJsonObject().getString("message"));
+ verify(serviceStoreWriter, never()).upload(null, null);
+ testContext.completeNow();
+ });
+ }
+
@Test
void addService_emptyRoles_returnsError(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
@@ -316,6 +340,32 @@ void addServiceNonEmptyRoles(Vertx vertx, VertxTestContext testContext) {
});
}
+ @Test
+ void addServiceWithRegex(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ setSites(new Site(123, "name1", false));
+
+ JsonArray ja = new JsonArray();
+ ja.add("GENERATOR");
+ ja.add("ID_READER");
+
+ JsonObject jo = new JsonObject();
+ jo.put("site_id", 123);
+ jo.put("name", "name1");
+ jo.put("roles", ja);
+ jo.put("link_id_regex", "valid");
+
+ post(vertx, testContext, "api/service/add", jo.encode(), response -> {
+ assertEquals(200, response.statusCode());
+ Service expectedService = new Service(1, 123, "name1", Set.of(Role.GENERATOR, Role.ID_READER), "valid");
+ checkServiceJson(expectedService, response.bodyAsJsonObject());
+ verify(serviceStoreWriter, times(1)).upload(List.of(expectedService), null);
+ testContext.completeNow();
+ });
+ }
+
+
@Test
void addServiceToExistingList(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
@@ -333,10 +383,11 @@ void addServiceToExistingList(Vertx vertx, VertxTestContext testContext) {
jo.put("site_id", 123);
jo.put("name", "name2");
jo.put("roles", ja);
+ jo.put("link_id_regex", "valid");
post(vertx, testContext, "api/service/add", jo.encode(), response -> {
assertEquals(200, response.statusCode());
- Service expectedService = new Service(2, 123, "name2", Set.of(Role.GENERATOR, Role.ID_READER));
+ Service expectedService = new Service(2, 123, "name2", Set.of(Role.GENERATOR, Role.ID_READER), "valid");
checkServiceJson(expectedService, response.bodyAsJsonObject());
verify(serviceStoreWriter, times(1)).upload(List.of(existingService, expectedService), null);
testContext.completeNow();
@@ -344,7 +395,7 @@ void addServiceToExistingList(Vertx vertx, VertxTestContext testContext) {
}
@Test
- void updateRolesMissingPayload(Vertx vertx, VertxTestContext testContext) {
+ void updateMissingPayload(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
postWithoutBody(vertx, testContext, "api/service/update", response -> {
@@ -357,12 +408,13 @@ void updateRolesMissingPayload(Vertx vertx, VertxTestContext testContext) {
@ParameterizedTest
@ValueSource(strings = {"service_id"})
- void updateRolesMissingParameters(String parameter, Vertx vertx, VertxTestContext testContext) {
+ void updateMissingParameters(String parameter, Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
JsonObject jo = new JsonObject();
jo.put("service_id", 1);
jo.put("roles", new JsonArray());
+ jo.put("link_id_regex", "valid");
jo.remove(parameter);
@@ -375,7 +427,7 @@ void updateRolesMissingParameters(String parameter, Vertx vertx, VertxTestContex
}
@Test
- void updateRolesBadServiceId(Vertx vertx, VertxTestContext testContext) {
+ void updateBadServiceId(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
setServices(new Service(1, 123, "name1", Set.of(Role.MAINTAINER)));
@@ -386,6 +438,7 @@ void updateRolesBadServiceId(Vertx vertx, VertxTestContext testContext) {
JsonObject jo = new JsonObject();
jo.put("service_id", 2);
jo.put("roles", ja);
+ jo.put("link_id_regex", "valid");
post(vertx, testContext, "api/service/update", jo.encode(), response -> {
assertEquals(404, response.statusCode());
@@ -440,6 +493,26 @@ void updateRoles(Vertx vertx, VertxTestContext testContext) {
});
}
+ @Test
+ void updateLinkIdRegex(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER));
+ setServices(existingService);
+
+ JsonObject jo = new JsonObject();
+ jo.put("service_id", 1);
+ jo.put("link_id_regex", "valid");
+
+ post(vertx, testContext, "api/service/update", jo.encode(), response -> {
+ assertEquals(200, response.statusCode());
+ existingService.setLinkIdRegex("valid");
+ checkServiceJson(existingService, response.bodyAsJsonObject());
+ verify(serviceStoreWriter, times(1)).upload(List.of(existingService), null);
+ testContext.completeNow();
+ });
+ }
+
@Test
void updateService_removeRolesNotUsedByServiceLinks_succeeds(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.PRIVILEGED);
@@ -538,6 +611,7 @@ void updateWithEmptyValues(Vertx vertx, VertxTestContext testContext) {
jo.put("service_id", 1);
jo.put("site_id", null);
jo.put("name", "");
+ jo.put("link_id_regex", "");
post(vertx, testContext, "api/service/update", jo.encode(), response -> {
assertEquals(200, response.statusCode());
@@ -629,6 +703,58 @@ void updateServiceDuplicateName(Vertx vertx, VertxTestContext testContext) {
});
}
+ @Test
+ void removeRegex(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER), "valid");
+ setServices(existingService);
+
+ JsonObject jo = new JsonObject();
+ jo.put("service_id", 1);
+
+ post(vertx, testContext, "api/service/remove-link-id-regex", jo.encode(), response -> {
+ assertEquals(200, response.statusCode());
+ existingService.setLinkIdRegex(null);
+ checkServiceJson(existingService, response.bodyAsJsonObject());
+ verify(serviceStoreWriter, times(1)).upload(List.of(existingService), null);
+ testContext.completeNow();
+ });
+ }
+
+ @Test
+ void removeRegexInvalidServiceId(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER), "valid");
+ setServices(existingService);
+
+ JsonObject jo = new JsonObject();
+ jo.put("service_id", 2);
+
+ post(vertx, testContext, "api/service/remove-link-id-regex", jo.encode(), response -> {
+ assertEquals(404, response.statusCode());
+ assertEquals("failed to find a service for service_id: 2", response.bodyAsJsonObject().getString("message"));
+ verify(serviceStoreWriter, never()).upload(null, null);
+ testContext.completeNow();
+ });
+ }
+
+ @Test
+ void removeRegexNoBody(Vertx vertx, VertxTestContext testContext) {
+ fakeAuth(Role.PRIVILEGED);
+
+ Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER), "valid");
+ setServices(existingService);
+
+ post(vertx, testContext, "api/service/remove-link-id-regex", "", response -> {
+ assertEquals(400, response.statusCode());
+ assertEquals("json payload required but not provided", response.bodyAsJsonObject().getString("message"));
+ verify(serviceStoreWriter, never()).upload(null, null);
+ testContext.completeNow();
+ });
+ }
+
@Test
void deleteService(Vertx vertx, VertxTestContext testContext) {
fakeAuth(Role.SUPER_USER);
diff --git a/webroot/adm/service.html b/webroot/adm/service.html
index 031b623b..26a308a2 100644
--- a/webroot/adm/service.html
+++ b/webroot/adm/service.html
@@ -22,6 +22,8 @@ Inputs
+
+
@@ -34,6 +36,7 @@ Operations
Get Service By Service Id
Add Service (PRIVILEGED)
Update Service (PRIVILEGED)
+ Remove Link Id Regex (PRIVILEGED)
@@ -87,8 +90,9 @@ Output
}
const roles = ($('#roles').val()).replace(/\s+/g, '').split(',').filter( (value, _, __) => value !== "");
+ const linkIdRegex = $('#linkIdRegex').val()
- doApiCall('POST', '/api/service/add', '#standardOutput', '#errorOutput', JSON.stringify({site_id: siteId, name: serviceName, roles: roles}));
+ doApiCall('POST', '/api/service/add', '#standardOutput', '#errorOutput', JSON.stringify({site_id: siteId, name: serviceName, roles: roles, link_id_regex: linkIdRegex}));
});
$('#doUpdate').on('click', function () {
@@ -98,10 +102,12 @@ Output
return
}
const siteId = parseInt($('#siteId').val())
- const serviceName = $('#serviceName').val()
- const roles = ($('#roles').val()).replace(/\s+/g, '').split(',').filter( (value, _, __) => value !== "");
+ const serviceName = $('#serviceName').val();
+ let roles = ($('#roles').val()).replace(/\s+/g, '').split(',').filter( (value, _, __) => value !== "");
+ roles = roles.length > 0 ? roles : null;
+ const linkIdRegex = $('#linkIdRegex').val()
- doApiCall('POST', '/api/service/update', '#standardOutput', '#errorOutput', JSON.stringify({service_id: serviceId, site_id: siteId, name: serviceName, roles: roles}));
+ doApiCall('POST', '/api/service/update', '#standardOutput', '#errorOutput', JSON.stringify({service_id: serviceId, site_id: siteId, name: serviceName, roles: roles, link_id_regex: linkIdRegex}));
});
$('#doDelete').on('click', function () {
@@ -117,6 +123,18 @@ Output
doApiCall('POST', '/api/service/delete', '#standardOutput', '#errorOutput', JSON.stringify({service_id: serviceId}));
});
+
+ $('#doRemoveLinkIdRegex').on('click', function () {
+ if (!confirm("Are you sure you want to remove the link id regex?")) {
+ return;
+ }
+ const serviceId = parseInt($('#serviceId').val())
+ if(!serviceId) {
+ $('#errorOutput').text("required parameters: service_id")
+ return
+ }
+ doApiCall('POST', '/api/service/remove-link-id-regex', '#standardOutput', '#errorOutput', JSON.stringify({service_id: serviceId}));
+ });
});