Skip to content

Commit 88a60e5

Browse files
authored
Merge pull request #515 from IABTechLab/ian-UID2-3134-validate-linkid-against-service-regex
validate link_id's against per-service regex
2 parents 02bb063 + 08feb87 commit 88a60e5

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

src/main/java/com/uid2/admin/vertx/service/ServiceLinkService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.slf4j.LoggerFactory;
2020

2121
import java.util.*;
22+
import java.util.regex.Pattern;
2223
import java.util.stream.Collectors;
2324

2425
import static com.uid2.admin.vertx.Endpoints.*;
@@ -118,6 +119,12 @@ private void handleServiceLinkAdd(RoutingContext rc) {
118119
return;
119120
}
120121

122+
String linkIdRegex = serviceProvider.getService(serviceId).getLinkIdRegex();
123+
if (!isValidLinkId(linkId, linkIdRegex)) {
124+
ResponseUtil.error(rc, 400, "link_id " + linkId + " does not match service_id " + serviceId + " link_id_regex: " + linkIdRegex);
125+
return;
126+
}
127+
121128
Set<Role> serviceRoles = serviceProvider.getService(serviceId).getRoles();
122129
final Set<Role> roles;
123130
try {
@@ -270,4 +277,11 @@ private Set<Role> validateRoles(JsonArray rolesToValidate, Set<Role> serviceRole
270277
}
271278
return roles;
272279
}
280+
281+
private boolean isValidLinkId(String linkId, String serviceRegex) {
282+
if (serviceRegex == null) {
283+
return true;
284+
}
285+
return Pattern.matches(serviceRegex, linkId);
286+
}
273287
}

src/main/java/com/uid2/admin/vertx/service/ServiceService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ public void setupRoutes(Router router) {
5858
synchronized (writeLock) {
5959
this.handleServiceAdd(ctx);
6060
}
61-
}, new AuditParams(Collections.emptyList(), List.of("site_id", "name", "roles")), Role.PRIVILEGED));
61+
}, new AuditParams(Collections.emptyList(), List.of("site_id", "name", "roles", "link_id_regex")), Role.PRIVILEGED));
6262
router.post(API_SERVICE_UPDATE.toString()).blockingHandler(auth.handle((ctx) -> {
6363
synchronized (writeLock) {
6464
this.handleUpdate(ctx);
6565
}
66-
}, new AuditParams(Collections.emptyList(), List.of("service_id", "site_id", "name", "roles")), Role.PRIVILEGED));
66+
}, new AuditParams(Collections.emptyList(), List.of("service_id", "site_id", "name", "roles", "link_id_regex")), Role.PRIVILEGED));
6767
router.post(API_SERVICE_DELETE.toString()).blockingHandler(auth.handle((ctx) -> {
6868
synchronized (writeLock) {
6969
this.handleDelete(ctx);

src/test/java/com/uid2/admin/vertx/ServiceLinkServiceTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.vertx.junit5.VertxTestContext;
1414
import org.junit.jupiter.api.Test;
1515
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.MethodSource;
1617
import org.junit.jupiter.params.provider.ValueSource;
1718

1819
import java.util.ArrayList;
@@ -584,4 +585,57 @@ void deleteServiceLink_invalidServiceId_returnsError(Vertx vertx, VertxTestConte
584585
testContext.completeNow();
585586
});
586587
}
588+
589+
@ParameterizedTest
590+
@MethodSource("linkIdRegexCases")
591+
void addServiceLink_linkIdRegex_validation(String linkIdRegex, String linkId, boolean expectSuccess,
592+
Vertx vertx, VertxTestContext testContext) {
593+
fakeAuth(Role.MAINTAINER);
594+
595+
setSites(new Site(123, "name1", false));
596+
setServices(new Service(1, 123, "name1", Set.of(Role.MAINTAINER), linkIdRegex));
597+
598+
JsonObject jo = new JsonObject();
599+
jo.put("link_id", linkId);
600+
jo.put("service_id", 1);
601+
jo.put("site_id", 123);
602+
jo.put("name", "name1");
603+
jo.put("roles", JsonArray.of(Role.MAINTAINER));
604+
605+
post(vertx, testContext, "api/service_link/add", jo.encode(), response -> {
606+
if (expectSuccess) {
607+
assertEquals(200, response.statusCode());
608+
ServiceLink expected = new ServiceLink(linkId, 1, 123, "name1", Set.of(Role.MAINTAINER));
609+
checkServiceLinkJson(expected, response.bodyAsJsonObject());
610+
verify(serviceLinkStoreWriter, times(1)).upload(List.of(expected), null);
611+
} else {
612+
assertEquals(400, response.statusCode());
613+
String expectedMsg = "link_id " + linkId + " does not match service_id 1 link_id_regex: " + linkIdRegex;
614+
assertEquals(expectedMsg, response.bodyAsJsonObject().getString("message"));
615+
verify(serviceLinkStoreWriter, never()).upload(any(), any());
616+
}
617+
618+
verify(serviceStoreWriter, never()).upload(null, null);
619+
testContext.completeNow();
620+
});
621+
}
622+
623+
private static java.util.stream.Stream<org.junit.jupiter.params.provider.Arguments> linkIdRegexCases() {
624+
return java.util.stream.Stream.of(
625+
org.junit.jupiter.params.provider.Arguments.of("link[0-9]+", "invalidLink", false),
626+
org.junit.jupiter.params.provider.Arguments.of("link[0-9]+", "link42", true),
627+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345", true), // snowflake valid
628+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "UID2_ENVIRONMENT", true), // snowflake valid
629+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "xy12345", false), // snowflake invalid, lowercase
630+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X", true), // snowflake valid, minimum length
631+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(256), true), // snowflake valid, maximum length
632+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(257), false), // snowflake invalid, exceeds maximum length
633+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", " XY12345", false), // snowflake invalid, leading whitespace
634+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345 ", false), // snowflake invalid, trailing whitespace
635+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY 12345", false), // snowflake invalid, whitespace in the middle
636+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "", false), // snowflake invalid, empty
637+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", " ", false), // snowflake invalid, just whitespace
638+
org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY_12345", true) // snowflake valid, used underscore
639+
);
640+
}
587641
}

0 commit comments

Comments
 (0)