Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OKAPI-1081: Reject invalid tenant ids #1347

Merged
merged 7 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions okapi-core/src/main/java/org/folio/okapi/MainVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ private Future<Void> startModuleManager() {
return moduleManager.init(vertx);
}

TenantManager getTenantManager() {
return tenantManager;
}

private Future<Void> startTenants() {
logger.info("startTenants");
return tenantManager.init(vertx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.folio.okapi.util.OkapiError;
import org.folio.okapi.util.ProxyContext;
import org.folio.okapi.util.TenantInstallOptions;
import org.folio.okapi.util.TenantValidator;

/**
* Okapi's built-in module. Managing /_/ endpoints.
Expand Down Expand Up @@ -750,22 +751,22 @@ private String location(ProxyContext pc, String id, String baseUri, String s) {
private Future<String> createTenant(ProxyContext pc, String body) {
try {
final TenantDescriptor td = JsonDecoder.decode(body, TenantDescriptor.class);
if (td.getId() == null || td.getId().isEmpty()) {
td.setId(UUID.randomUUID().toString());
}
final String tenantId = td.getId();
if (!tenantId.matches("^[a-z0-9_-]+$")) {
return Future.failedFuture(
new OkapiError(ErrorType.USER, messages.getMessage("11601", tenantId)));
}
Tenant t = new Tenant(td);
return tenantManager.insert(t).map(res ->
location(pc, tenantId, null, Json.encodePrettily(t.getDescriptor())));
return validateTenantId(td)
.compose(x -> tenantManager.insert(new Tenant(td)))
.map(x -> location(pc, td.getId(), null, Json.encodePrettily(td)));
} catch (DecodeException ex) {
return Future.failedFuture(new OkapiError(ErrorType.USER, ex.getMessage()));
}
}

static Future<Void> validateTenantId(TenantDescriptor td) {
if (td.getId() == null || td.getId().isEmpty()) {
td.setId(("t" + UUID.randomUUID().toString().replace("-", ""))
julianladisch marked this conversation as resolved.
Show resolved Hide resolved
.substring(0, TenantValidator.MAX_LENGTH));
}
return TenantValidator.validate(td.getId());
}

private Future<String> updateTenant(String tenantId, String body) {
try {
final TenantDescriptor td = JsonDecoder.decode(body, TenantDescriptor.class);
Expand Down
56 changes: 56 additions & 0 deletions okapi-core/src/main/java/org/folio/okapi/util/TenantValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.folio.okapi.util;

import io.vertx.core.Future;
import java.util.regex.Pattern;
import org.folio.okapi.common.ErrorType;
import org.folio.okapi.common.Messages;

/**
* Validate a tenant ID to match ^[a-z][a-z0-9]{0,30}$ as required by
* <a href="https://folio-org.atlassian.net/wiki/display/TC/ADR-000002+-+Tenant+Id+and+Module+Name+Restrictions">
* https://folio-org.atlassian.net/wiki/display/TC/ADR-000002+-+Tenant+Id+and+Module+Name+Restrictions</a>
* Technical Council decision.
*/
public final class TenantValidator {
/** Maximum length of a tenant ID. */
public static final int MAX_LENGTH = 31;

// multi-byte sequences forbidden in pattern, so char length = byte length
@SuppressWarnings("java:S5867") // we want to forbid Unicode characters, therefore we
// suppress warning "Unicode-aware versions of character classes should be preferred"
private static final String TENANT_PATTERN_STRING = "^[a-z][a-z0-9]{0,30}$";
private static final Pattern TENANT_PATTERN = Pattern.compile(TENANT_PATTERN_STRING);
private static final Pattern STARTS_WITH_DIGIT = Pattern.compile("^\\d");
private static final Messages MESSAGES = Messages.getInstance();

private TenantValidator() {
throw new UnsupportedOperationException("Cannot instantiate utility class.");
}

/**
* Validate tenantId against ^[a-z][a-z0-9]{0,30}$ and return a failed Future with
* clear violation explanation message on validation failure.
*/
public static Future<Void> validate(String tenantId) {
if (TENANT_PATTERN.matcher(tenantId).matches()) {
return Future.succeededFuture();
}

String message;

if (tenantId.contains("_")) {
message = "11609";
} else if (tenantId.contains("-")) {
message = "11610";
} else if (tenantId.length() > MAX_LENGTH) {
message = "11611";
} else if (STARTS_WITH_DIGIT.matcher(tenantId).matches()) {
message = "11612";
} else {
message = "11601";
}

return Future.failedFuture(new OkapiError(ErrorType.USER,
MESSAGES.getMessage(message, tenantId)));
}
}
2 changes: 1 addition & 1 deletion okapi-core/src/main/raml/TenantDescriptor.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"additionalProperties" : false,
"properties": {
"id": {
"description": "Tenant ID",
"description": "Tenant ID. A new tenant ID created via the POST /_/proxy/tenants API must match the ^[a-z][a-z0-9]{0,30}$ regexp as required by https://folio-org.atlassian.net/wiki/spaces/TC/pages/5053983/DR-000002+-+Tenant+Id+and+Module+Name+Restrictions Technical Council decision. All other APIs also accept a legacy tenant ID that matches ^[a-z0-9_-]+$ regexp and may pose security issues as explained on https://folio-org.atlassian.net/wiki/spaces/DD/pages/1779867/Tenant+Id+and+Module+Name+Restrictions",
"type": "string"
},
"name": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,15 @@

#InternalModule
11600=Error in encoding location id {0}. {1}
11601=Invalid tenant id {0}
11602=Tenant.id={0} id={1}
11601=Invalid tenant id, may only contain a-z and 0-9 and must match '[a-z][a-z0-9]{0,30}' but it is {0}
11602=The tenant id must not be changed, old=''{1}'', new=''{0}''
11603=Can not delete the superTenant {0}
11604=unknown orderBy field: {0}
11605=invalid order value: {0}
11606=Module.id={0} id={1}
11607=Unhandled internal module path={0}
11608=Bad format for parameter {0}. {1}

11609=Tenant id must not contain underscore: {0}
11610=Tenant id must not contain minus: {0}
11611=Tenant id must not exceed 31 characters: {0}
11612=Tenant id must not start with a digit: {0}
Loading