sarankk commented on code in PR #273:
URL: https://github.com/apache/cassandra-sidecar/pull/273#discussion_r2495290419
##########
docs/src/user.adoc:
##########
@@ -402,11 +402,14 @@ The `metrics` configuration defines how Cassandra Sidecar
exposes metrics over J
This section defines input validations for Cassandra keyspace and directory
names which are used for SSTable imports. The following properties are defined:
+* `validator`: The implementation to use for the validation of Casandra inputs.
+** `class_name`: The name of the class implementing the
CassandraInputValidator interface. Out of the box Cassandra Sidecar provides
org.apache.cassandra.sidecar.utils.{RegexBasedCassandraInputValidator,
FastCassandraInputValidator}.
+** `parameters`: Configuration parameters that are only applicable to the
FastCassandraInputValidator implementation.
Review Comment:
Can you also add the new `FastCassandraInputValidator` based parameters
`valid_terminations`, `valid_restricted_terminations` to docs?
##########
server/src/test/resources/config/sidecar_validation_configuration.yaml:
##########
@@ -25,11 +25,16 @@ sidecar:
execute_interval: 50ms
cassandra_input_validation:
+ validator:
+ - class_name: org.apache.cassandra.sidecar.utils.FastCassandraInputValidator
+ parameters:
+ valid_terminations: ".abc,.def"
+ valid_restricted_terminations: ".xml"
forbidden_keyspaces:
- a
- b
- c
allowed_chars_for_directory: "[a-z]+"
allowed_chars_for_quoted_name: "[A-Z]+"
- allowed_chars_for_component_name: "(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "(.db|TOC.txt)"
+ allowed_chars_for_component_name: "(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
Review Comment:
Nit: Remove regex validator parameters since they are not relevant.
##########
server/src/main/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidator.java:
##########
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.utils;
+
+import java.io.File;
+import java.util.Objects;
+
+import org.apache.cassandra.sidecar.common.server.data.Name;
+import org.apache.cassandra.sidecar.common.utils.Preconditions;
+import
org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import
org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+import
org.apache.cassandra.sidecar.exceptions.ForbiddenCassandraInputException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.VisibleForTesting;
+
+/**
+ * Miscellaneous methods used for validation.
+ */
+public class RegexBasedCassandraInputValidator implements
CassandraInputValidator
+{
+ protected final CassandraInputValidationConfiguration
validationConfiguration;
+
+ @VisibleForTesting
+ RegexBasedCassandraInputValidator()
+ {
+ this(new CassandraInputValidationConfigurationImpl());
+ }
+
+ /**
+ * Constructs a new object with the provided {@code
validationConfiguration}
+ *
+ * @param validationConfiguration a validation configuration
+ */
+ public
RegexBasedCassandraInputValidator(CassandraInputValidationConfiguration
validationConfiguration)
+ {
+ this.validationConfiguration = validationConfiguration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Name validateKeyspaceName(@NotNull String keyspace)
+ {
+ Name name = new Name(keyspace);
+ validateNamePattern(name, "keyspace");
+
+ if (validationConfiguration.forbiddenKeyspaces().contains(name.name()))
Review Comment:
Nit: can we move forbidden check before validating pattern
##########
server/src/main/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidator.java:
##########
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.utils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.cassandra.sidecar.common.server.data.Name;
+import org.apache.cassandra.sidecar.common.utils.Preconditions;
+import
org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import
org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+import
org.apache.cassandra.sidecar.exceptions.ForbiddenCassandraInputException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.VisibleForTesting;
+
+/**
+ * An implementation of the {@link CassandraInputValidator} that does not use
regular expressions
+ * for validations and uses optimized validations.
+ */
+public class FastCassandraInputValidator extends
RegexBasedCassandraInputValidator
+{
+ /**
+ * Longest permissible keyspace name
+ */
+ public static final int KEYSPACE_NAME_LENGTH = 48;
+ /**
+ * Longest permissible table name. See CASSANDRA-20389 (Create table fails
on long table names) for
+ * details about the maximum length for table names.
+ */
+ public static final int TABLE_NAME_LENGTH = 222;
+ /**
+ * Default valid component name terminations
+ */
+ public static final List<String> DEFAULT_VALID_TERMINATIONS =
List.of(".db", ".cql", ".json", ".crc32", "TOC.txt");
+
+ /**
+ * Default valid component name terminations for restricted component names
+ */
+ public static final List<String> DEFAULT_VALID_RESTRICTED_TERMINATIONS =
List.of(".db", "TOC.txt");
+
+ @VisibleForTesting
+ final List<String> validTerminations;
+ @VisibleForTesting
+ final List<String> validRestrictedTerminations;
+
+ @VisibleForTesting
+ public FastCassandraInputValidator()
+ {
+ this(new CassandraInputValidationConfigurationImpl());
+ }
+
+ /**
+ * Constructs a new object with the provided {@code
validationConfiguration}
+ *
+ * @param validationConfiguration a validation configuration
+ */
+ public FastCassandraInputValidator(CassandraInputValidationConfiguration
validationConfiguration)
+ {
+ super(validationConfiguration);
+ Map<String, String> configMap =
validationConfiguration.validatorConfiguration().namedParameters();
+ validTerminations = parseConfiguredOrDefault(configMap,
"valid_terminations", DEFAULT_VALID_TERMINATIONS);
+ validRestrictedTerminations = parseConfiguredOrDefault(configMap,
"valid_restricted_terminations", DEFAULT_VALID_RESTRICTED_TERMINATIONS);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Name validateKeyspaceName(@NotNull String keyspace) throws
NullPointerException
+ {
+ Name name = new Name(keyspace);
+ validateNameLength(name, "keyspace", KEYSPACE_NAME_LENGTH);
+ validateNamePattern(name, "keyspace");
+
+ if (validationConfiguration.forbiddenKeyspaces().contains(name.name()))
+ throw new ForbiddenCassandraInputException("Forbidden keyspace: "
+ keyspace);
+
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Name validateTableName(@NotNull String tableName)
+ {
+ Name name = new Name(tableName);
+ validateNameLength(name, "table name", TABLE_NAME_LENGTH);
+ validateNamePattern(name, "table name");
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String validateComponentName(@NotNull String componentName)
+ {
+ Objects.requireNonNull(componentName, "componentName must not be
null");
+ Preconditions.checkArgument(!componentName.isEmpty(), () ->
"componentName cannot be empty");
+ validateComponentName(componentName, validTerminations);
+ return componentName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String validateRestrictedComponentName(@NotNull String
componentName)
+ {
+ Objects.requireNonNull(componentName, "componentName must not be
null");
+ Preconditions.checkArgument(!componentName.isEmpty(), () ->
"componentName cannot be empty");
+ validateComponentName(componentName, validRestrictedTerminations);
+ return componentName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void validateIndexName(String secondaryIndexName)
+ {
+ Preconditions.checkArgument(!secondaryIndexName.isEmpty(),
"secondaryIndexName cannot be empty");
+ if (secondaryIndexName.charAt(0) != '.')
+ throw new CassandraInputException("Invalid secondary index name: "
+ secondaryIndexName);
+ validateNamePattern(secondaryIndexName, secondaryIndexName, false,
"secondary index", 1 /* skip the first character */);
+ }
+
+ /**
+ * Validates that the {@code name} is a valid name in Cassandra as defined
by the grammar in
+ * <a
href="https://cassandra.apache.org/doc/4.1/cassandra/cql/ddl.html#common-definitions">Cassandra
CQL common
+ * definitions</a>
+ *
+ * @param name name to validate
+ * @param exceptionHint hint to add in the exception message
+ * @throws CassandraInputException when the {@code unquotedInput} has
invalid characters
+ */
+ @Override
+ public void validateNamePattern(Name name, String exceptionHint)
+ {
+ validateNamePattern(name.name(), name.maybeQuotedName(),
name.isSourceQuoted(), exceptionHint, 0);
+ }
+
+ /**
+ * Validates that the {@code name} is a valid name in Cassandra as defined
by the grammar in
+ * <a
href="https://cassandra.apache.org/doc/4.1/cassandra/cql/ddl.html#common-definitions">Cassandra
CQL common
+ * definitions</a>. The validation will only take into account the start
index.
+ *
+ * @param unquotedName the unquoted name to validate
+ * @param maybeQuotedName the name that maybe quoted
+ * @param isSourceQuoted whether the source will be quoted
+ * @param exceptionHint hint to add in the exception message
+ * @param startIndex start index
+ * @throws CassandraInputException when the {@code unquotedName} has
invalid characters
+ */
+ protected void validateNamePattern(String unquotedName, String
maybeQuotedName, boolean isSourceQuoted,
+ String exceptionHint, int startIndex)
+ {
+ char c;
+ if (!isSourceQuoted)
+ {
+ // Validate the first character. Unquoted names can only begin
with a letter
+ c = unquotedName.charAt(startIndex++);
+ if (!isLetter(c))
+ throw new CassandraInputException("Invalid characters in " +
exceptionHint + ": " + maybeQuotedName);
+ }
+
+ while (startIndex < unquotedName.length())
+ {
+ c = unquotedName.charAt(startIndex++);
+ if (!isAlphanumeric(c) && !isUnderscore(c))
+ throw new CassandraInputException("Invalid characters in " +
exceptionHint + ": " + maybeQuotedName);
+ }
+ }
+
+ /**
+ * Validates that the {@code name} has valid length
+ *
+ * @param name name to validate
+ * @param exceptionHint hint to add in the exception message
+ * @param maxNameLength the maximum length for the name
+ * @throws CassandraInputException when the length of the {@code
unquotedInput} is empty or larger than
+ * {@code maxNameLength}
+ */
+ protected void validateNameLength(Name name, String exceptionHint, int
maxNameLength)
+ {
+ String unquotedInput = name.name();
+ if (unquotedInput.isEmpty() || unquotedInput.length() > maxNameLength)
+ throw new CassandraInputException("Invalid length " +
unquotedInput.length() +
+ " for " + exceptionHint + ": " +
name.maybeQuotedName());
+ }
+
+ /**
+ * Validates that the {@code componentName} has valid characters, and it
ends with one of the
+ * {@code validTerminations}.
+ *
+ * @param componentName the name of the component to validate
+ * @param validTerminations a list of valid terminations for the component
name
+ */
+ protected void validateComponentName(String componentName, List<String>
validTerminations)
+ {
+ char c;
+ int lastIndexOfAllowedTermination =
lastIndexOfAllowedTermination(componentName, validTerminations);
+
+ if (lastIndexOfAllowedTermination < 1)
Review Comment:
Could we have a custom component in Cassandra with 1 letter SSTable format?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]